diff options
author | erg@google.com <erg@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-08-22 22:13:29 +0000 |
---|---|---|
committer | erg@google.com <erg@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-08-22 22:13:29 +0000 |
commit | bacf470d2cd46c0d3b145675e471ab9c6976cee8 (patch) | |
tree | 08b07add5d1736cb5b435dbcc8fdca968b6b97b8 /content | |
parent | 0adf649c85353e90478a994dac74e9f1575b7e6e (diff) | |
download | chromium_src-bacf470d2cd46c0d3b145675e471ab9c6976cee8.zip chromium_src-bacf470d2cd46c0d3b145675e471ab9c6976cee8.tar.gz chromium_src-bacf470d2cd46c0d3b145675e471ab9c6976cee8.tar.bz2 |
content: Move render_widget_host_view_gtk to content/
This also moves some other files:
- OwnedWidgetGtk now goes in ui/base/gtk/
- TruncateString moved from l10n_util:: to ui::
- GtkIMContextWrapper has part of its code split into chrome/ (IDC using code goes in RenderViewContextMenu) and the rest go in content/ (gtk using code goes with GtkIMContextWrapper).
- gtk_key_bindings_handler[_unittest] now goes in content, as it's a utility class to RenderWidgetHostGtk.
BUG=93804
TEST=existing unit tests
Review URL: http://codereview.chromium.org/7669040
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@97750 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'content')
-rw-r--r-- | content/browser/renderer_host/gtk_im_context_wrapper.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 | 6 | ||||
-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, 3116 insertions, 1 deletions
diff --git a/content/browser/renderer_host/gtk_im_context_wrapper.cc b/content/browser/renderer_host/gtk_im_context_wrapper.cc new file mode 100644 index 0000000..c5c4e03 --- /dev/null +++ b/content/browser/renderer_host/gtk_im_context_wrapper.cc @@ -0,0 +1,619 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/renderer_host/gtk_im_context_wrapper.h" + +#include <gdk/gdk.h> +#include <gdk/gdkkeysyms.h> +#include <gtk/gtk.h> + +#include <algorithm> + +#include "base/logging.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "content/browser/renderer_host/render_widget_host.h" +#include "content/browser/renderer_host/render_widget_host_view_gtk.h" +#include "content/common/native_web_keyboard_event.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebCompositionUnderline.h" +#include "ui/base/gtk/gtk_im_context_util.h" +#include "ui/gfx/gtk_util.h" +#include "ui/gfx/rect.h" + +namespace { +// Copied from third_party/WebKit/Source/WebCore/page/EventHandler.cpp +// +// Match key code of composition keydown event on windows. +// IE sends VK_PROCESSKEY which has value 229; +// +// Please refer to following documents for detals: +// - Virtual-Key Codes +// http://msdn.microsoft.com/en-us/library/ms645540(VS.85).aspx +// - How the IME System Works +// http://msdn.microsoft.com/en-us/library/cc194848.aspx +// - ImmGetVirtualKey Function +// http://msdn.microsoft.com/en-us/library/dd318570(VS.85).aspx +const int kCompositionEventKeyCode = 229; +} // namespace + +// ui::CompositionUnderline should be identical to +// WebKit::WebCompositionUnderline, so that we can do reinterpret_cast safely. +// TODO(suzhe): remove it after migrating all code in chrome to use +// ui::CompositionUnderline. +COMPILE_ASSERT(sizeof(ui::CompositionUnderline) == + sizeof(WebKit::WebCompositionUnderline), + ui_CompositionUnderline__WebKit_WebCompositionUnderline_diff); + +GtkIMContextWrapper::GtkIMContextWrapper(RenderWidgetHostViewGtk* host_view) + : host_view_(host_view), + context_(gtk_im_multicontext_new()), + context_simple_(gtk_im_context_simple_new()), + is_focused_(false), + is_composing_text_(false), + is_enabled_(false), + is_in_key_event_handler_(false), + is_composition_changed_(false), + suppress_next_commit_(false), + last_key_code_(0), + last_key_was_up_(false), + last_key_filtered_no_result_(false) { + DCHECK(context_); + DCHECK(context_simple_); + + // context_ and context_simple_ share the same callback handlers. + // All data come from them are treated equally. + // context_ is for full input method support. + // context_simple_ is for supporting dead/compose keys when input method is + // disabled by webkit, eg. in password input box. + g_signal_connect(context_, "preedit_start", + G_CALLBACK(HandlePreeditStartThunk), this); + g_signal_connect(context_, "preedit_end", + G_CALLBACK(HandlePreeditEndThunk), this); + g_signal_connect(context_, "preedit_changed", + G_CALLBACK(HandlePreeditChangedThunk), this); + g_signal_connect(context_, "commit", + G_CALLBACK(HandleCommitThunk), this); + + g_signal_connect(context_simple_, "preedit_start", + G_CALLBACK(HandlePreeditStartThunk), this); + g_signal_connect(context_simple_, "preedit_end", + G_CALLBACK(HandlePreeditEndThunk), this); + g_signal_connect(context_simple_, "preedit_changed", + G_CALLBACK(HandlePreeditChangedThunk), this); + g_signal_connect(context_simple_, "commit", + G_CALLBACK(HandleCommitThunk), this); + + GtkWidget* widget = host_view->native_view(); + DCHECK(widget); + + g_signal_connect(widget, "realize", + G_CALLBACK(HandleHostViewRealizeThunk), this); + g_signal_connect(widget, "unrealize", + G_CALLBACK(HandleHostViewUnrealizeThunk), this); + + // Set client window if the widget is already realized. + HandleHostViewRealize(widget); +} + +GtkIMContextWrapper::~GtkIMContextWrapper() { + if (context_) + g_object_unref(context_); + if (context_simple_) + g_object_unref(context_simple_); +} + +void GtkIMContextWrapper::ProcessKeyEvent(GdkEventKey* event) { + suppress_next_commit_ = false; + + // Indicates preedit-changed and commit signal handlers that we are + // processing a key event. + is_in_key_event_handler_ = true; + // Reset this flag so that we can know if preedit is changed after + // processing this key event. + is_composition_changed_ = false; + // Clear it so that we can know if something needs committing after + // processing this key event. + commit_text_.clear(); + + // According to Document Object Model (DOM) Level 3 Events Specification + // Appendix A: Keyboard events and key identifiers + // http://www.w3.org/TR/DOM-Level-3-Events/keyset.html: + // The event sequence would be: + // 1. keydown + // 2. textInput + // 3. keyup + // + // So keydown must be sent to webkit before sending input method result, + // while keyup must be sent afterwards. + // However on Windows, if a keydown event has been processed by IME, its + // virtual keycode will be changed to VK_PROCESSKEY(0xE5) before being sent + // to application. + // To emulate the windows behavior as much as possible, we need to send the + // key event to the GtkIMContext object first, and decide whether or not to + // send the original key event to webkit according to the result from IME. + // + // If IME is enabled by WebKit, this event will be dispatched to context_ + // to get full IME support. Otherwise it'll be dispatched to + // context_simple_, so that dead/compose keys can still work. + // + // It sends a "commit" signal when it has a character to be inserted + // even when we use a US keyboard so that we can send a Char event + // (or an IME event) to the renderer in our "commit"-signal handler. + // We should send a KeyDown (or a KeyUp) event before dispatching this + // event to the GtkIMContext object (and send a Char event) so that WebKit + // can dispatch the JavaScript events in the following order: onkeydown(), + // onkeypress(), and onkeyup(). (Many JavaScript pages assume this.) + gboolean filtered = false; + if (is_enabled_) { + filtered = gtk_im_context_filter_keypress(context_, event); + } else { + filtered = gtk_im_context_filter_keypress(context_simple_, event); + } + + // Reset this flag here, as it's only used in input method callbacks. + is_in_key_event_handler_ = false; + + NativeWebKeyboardEvent wke(event); + + // If the key event was handled by the input method, then we need to prevent + // RenderView::UnhandledKeyboardEvent() from processing it. + // Otherwise unexpected result may occur. For example if it's a + // Backspace key event, the browser may go back to previous page. + // We just send all keyup events to the browser to avoid breaking the + // browser's MENU key function, which is actually the only keyup event + // handled in the browser. + if (filtered && event->type == GDK_KEY_PRESS) + wke.skip_in_browser = true; + + const int key_code = wke.windowsKeyCode; + const bool has_result = HasInputMethodResult(); + + // Send filtered keydown event before sending IME result. + // In order to workaround http://crosbug.com/6582, we only send a filtered + // keydown event if it generated any input method result. + if (event->type == GDK_KEY_PRESS && filtered && has_result) + ProcessFilteredKeyPressEvent(&wke); + + // Send IME results. In most cases, it's only available if the key event + // is filtered by IME. But in rare cases, an unfiltered key event may also + // generate IME results. + // Any IME results generated by a unfiltered key down event must be sent + // before the key down event, to avoid some tricky issues. For example, + // when using latin-post input method, pressing 'a' then Backspace, may + // generate following events in sequence: + // 1. keydown 'a' (filtered) + // 2. preedit changed to "a" + // 3. keyup 'a' (unfiltered) + // 4. keydown Backspace (unfiltered) + // 5. commit "a" + // 6. preedit end + // 7. keyup Backspace (unfiltered) + // + // In this case, the input box will be in a strange state if keydown + // Backspace is sent to webkit before commit "a" and preedit end. + if (has_result) + ProcessInputMethodResult(event, filtered); + + // Send unfiltered keydown and keyup events after sending IME result. + if (event->type == GDK_KEY_PRESS && !filtered) { + ProcessUnfilteredKeyPressEvent(&wke); + } else if (event->type == GDK_KEY_RELEASE) { + // In order to workaround http://crosbug.com/6582, we need to suppress + // the keyup event if corresponding keydown event was suppressed, or + // the last key event was a keyup event with the same keycode. + const bool suppress = (last_key_code_ == key_code) && + (last_key_was_up_ || last_key_filtered_no_result_); + + if (!suppress) + host_view_->ForwardKeyboardEvent(wke); + } + + last_key_code_ = key_code; + last_key_was_up_ = (event->type == GDK_KEY_RELEASE); + last_key_filtered_no_result_ = (filtered && !has_result); +} + +void GtkIMContextWrapper::UpdateInputMethodState( + ui::TextInputType type, + bool can_compose_inline, + const gfx::Rect& caret_rect) { + suppress_next_commit_ = false; + + // The renderer has updated its IME status. + // Control the GtkIMContext object according to this status. + if (!context_ || !is_focused_) + return; + + DCHECK(!is_in_key_event_handler_); + + bool is_enabled = (type != ui::TEXT_INPUT_TYPE_NONE && + type != ui::TEXT_INPUT_TYPE_PASSWORD); + if (is_enabled_ != is_enabled) { + is_enabled_ = is_enabled; + if (is_enabled) + gtk_im_context_focus_in(context_); + else + gtk_im_context_focus_out(context_); + } + + if (is_enabled) { + // If the focused element supports inline rendering of composition text, + // we receive and send related events to it. Otherwise, the events related + // to the updates of composition text are directed to the candidate window. + gtk_im_context_set_use_preedit(context_, can_compose_inline); + // Updates the position of the IME candidate window. + // The position sent from the renderer is a relative one, so we need to + // attach the GtkIMContext object to this window before changing the + // position. + GdkRectangle cursor_rect(caret_rect.ToGdkRectangle()); + gtk_im_context_set_cursor_location(context_, &cursor_rect); + } +} + +void GtkIMContextWrapper::OnFocusIn() { + if (is_focused_) + return; + + // Tracks the focused state so that we can give focus to the + // GtkIMContext object correctly later when IME is enabled by WebKit. + is_focused_ = true; + + last_key_code_ = 0; + last_key_was_up_ = false; + last_key_filtered_no_result_ = false; + + // Notify the GtkIMContext object of this focus-in event only if IME is + // enabled by WebKit. + if (is_enabled_) + gtk_im_context_focus_in(context_); + + // context_simple_ is always enabled. + // Actually it doesn't care focus state at all. + gtk_im_context_focus_in(context_simple_); + + // Enables RenderWidget's IME related events, so that we can be notified + // when WebKit wants to enable or disable IME. + if (host_view_->GetRenderWidgetHost()) + host_view_->GetRenderWidgetHost()->SetInputMethodActive(true); +} + +void GtkIMContextWrapper::OnFocusOut() { + if (!is_focused_) + return; + + // Tracks the focused state so that we won't give focus to the + // GtkIMContext object unexpectly. + is_focused_ = false; + + // Notify the GtkIMContext object of this focus-out event only if IME is + // enabled by WebKit. + if (is_enabled_) { + // To reset the GtkIMContext object and prevent data loss. + ConfirmComposition(); + gtk_im_context_focus_out(context_); + } + + // To make sure it'll be in correct state when focused in again. + gtk_im_context_reset(context_simple_); + gtk_im_context_focus_out(context_simple_); + + is_composing_text_ = false; + + // Disable RenderWidget's IME related events to save bandwidth. + if (host_view_->GetRenderWidgetHost()) + host_view_->GetRenderWidgetHost()->SetInputMethodActive(false); +} + +#if !defined(TOOLKIT_VIEWS) +// Not defined for views because the views context menu doesn't +// implement input methods yet. +GtkWidget* GtkIMContextWrapper::BuildInputMethodsGtkMenu() { + GtkWidget* submenu = gtk_menu_new(); + gtk_im_multicontext_append_menuitems(GTK_IM_MULTICONTEXT(context_), + GTK_MENU_SHELL(submenu)); + return submenu; +} +#endif + +void GtkIMContextWrapper::CancelComposition() { + if (!is_enabled_) + return; + + DCHECK(!is_in_key_event_handler_); + + // To prevent any text from being committed when resetting the |context_|; + is_in_key_event_handler_ = true; + suppress_next_commit_ = true; + + gtk_im_context_reset(context_); + gtk_im_context_reset(context_simple_); + + if (is_focused_) { + // Some input methods may not honour the reset call. Focusing out/in the + // |context_| to make sure it gets reset correctly. + gtk_im_context_focus_out(context_); + gtk_im_context_focus_in(context_); + } + + is_composing_text_ = false; + composition_.Clear(); + commit_text_.clear(); + + is_in_key_event_handler_ = false; +} + +bool GtkIMContextWrapper::NeedCommitByForwardingCharEvent() const { + // If there is no composition text and has only one character to be + // committed, then the character will be send to webkit as a Char event + // instead of a confirmed composition text. + // It should be fine to handle BMP character only, as non-BMP characters + // can always be committed as confirmed composition text. + return !is_composing_text_ && commit_text_.length() == 1; +} + +bool GtkIMContextWrapper::HasInputMethodResult() const { + return commit_text_.length() || is_composition_changed_; +} + +void GtkIMContextWrapper::ProcessFilteredKeyPressEvent( + NativeWebKeyboardEvent* wke) { + // If IME has filtered this event, then replace virtual key code with + // VK_PROCESSKEY. See comment in ProcessKeyEvent() for details. + // It's only required for keydown events. + // To emulate windows behavior, when input method is enabled, if the commit + // text can be emulated by a Char event, then don't do this replacement. + if (!NeedCommitByForwardingCharEvent()) { + wke->windowsKeyCode = kCompositionEventKeyCode; + // keyidentifier must be updated accordingly, otherwise this key event may + // still be processed by webkit. + wke->setKeyIdentifierFromWindowsKeyCode(); + } + host_view_->ForwardKeyboardEvent(*wke); +} + +void GtkIMContextWrapper::ProcessUnfilteredKeyPressEvent( + NativeWebKeyboardEvent* wke) { + // Send keydown event as it, because it's not filtered by IME. + host_view_->ForwardKeyboardEvent(*wke); + + // IME is disabled by WebKit or the GtkIMContext object cannot handle + // this key event. + // This case is caused by two reasons: + // 1. The given key event is a control-key event, (e.g. return, page up, + // page down, tab, arrows, etc.) or; + // 2. The given key event is not a control-key event but printable + // characters aren't assigned to the event, (e.g. alt+d, etc.) + // Create a Char event manually from this key event and send it to the + // renderer when this Char event contains a printable character which + // should be processed by WebKit. + // isSystemKey will be set to true if this key event has Alt modifier, + // see WebInputEventFactory::keyboardEvent() for details. + if (wke->text[0]) { + wke->type = WebKit::WebInputEvent::Char; + wke->skip_in_browser = true; + host_view_->ForwardKeyboardEvent(*wke); + } +} + +void GtkIMContextWrapper::ProcessInputMethodResult(const GdkEventKey* event, + bool filtered) { + RenderWidgetHost* host = host_view_->GetRenderWidgetHost(); + if (!host) + return; + + bool committed = false; + // We do commit before preedit change, so that we can optimize some + // unnecessary preedit changes. + if (commit_text_.length()) { + if (filtered && NeedCommitByForwardingCharEvent()) { + // Send a Char event when we input a composed character without IMEs + // so that this event is to be dispatched to onkeypress() handlers, + // autofill, etc. + // Only commit text generated by a filtered key down event can be sent + // as a Char event, because a unfiltered key down event will probably + // generate another Char event. + // TODO(james.su@gmail.com): Is it necessary to support non BMP chars + // here? + NativeWebKeyboardEvent char_event(commit_text_[0], + event->state, + base::Time::Now().ToDoubleT()); + char_event.skip_in_browser = true; + host_view_->ForwardKeyboardEvent(char_event); + } else { + committed = true; + // Send an IME event. + // Unlike a Char event, an IME event is NOT dispatched to onkeypress() + // handlers or autofill. + host->ImeConfirmComposition(commit_text_); + // Set this flag to false, as this composition session has been + // finished. + is_composing_text_ = false; + } + } + + // Send preedit text only if it's changed. + // If a text has been committed, then we don't need to send the empty + // preedit text again. + if (is_composition_changed_) { + if (composition_.text.length()) { + // Another composition session has been started. + is_composing_text_ = true; + // TODO(suzhe): convert both renderer_host and renderer to use + // ui::CompositionText. + const std::vector<WebKit::WebCompositionUnderline>& underlines = + reinterpret_cast<const std::vector<WebKit::WebCompositionUnderline>&>( + composition_.underlines); + host->ImeSetComposition(composition_.text, underlines, + composition_.selection.start(), + composition_.selection.end()); + } else if (!committed) { + host->ImeCancelComposition(); + } + } +} + +void GtkIMContextWrapper::ConfirmComposition() { + if (!is_enabled_) + return; + + DCHECK(!is_in_key_event_handler_); + + if (is_composing_text_) { + if (host_view_->GetRenderWidgetHost()) + host_view_->GetRenderWidgetHost()->ImeConfirmComposition(); + + // Reset the input method. + CancelComposition(); + } +} + +void GtkIMContextWrapper::HandleCommit(const string16& text) { + if (suppress_next_commit_) { + suppress_next_commit_ = false; + return; + } + + // Append the text to the buffer, because commit signal might be fired + // multiple times when processing a key event. + commit_text_.append(text); + // Nothing needs to do, if it's currently in ProcessKeyEvent() + // handler, which will send commit text to webkit later. Otherwise, + // we need send it here. + // It's possible that commit signal is fired without a key event, for + // example when user input via a voice or handwriting recognition software. + // In this case, the text must be committed directly. + if (!is_in_key_event_handler_ && host_view_->GetRenderWidgetHost()) { + // Workaround http://crbug.com/45478 by sending fake key down/up events. + SendFakeCompositionKeyEvent(WebKit::WebInputEvent::RawKeyDown); + host_view_->GetRenderWidgetHost()->ImeConfirmComposition(text); + SendFakeCompositionKeyEvent(WebKit::WebInputEvent::KeyUp); + } +} + +void GtkIMContextWrapper::HandlePreeditStart() { + // Ignore preedit related signals triggered by CancelComposition() method. + if (suppress_next_commit_) + return; + is_composing_text_ = true; +} + +void GtkIMContextWrapper::HandlePreeditChanged(const gchar* text, + PangoAttrList* attrs, + int cursor_position) { + // Ignore preedit related signals triggered by CancelComposition() method. + if (suppress_next_commit_) + return; + + // Don't set is_composition_changed_ to false if there is no change, because + // this handler might be called multiple times with the same data. + is_composition_changed_ = true; + composition_.Clear(); + + ui::ExtractCompositionTextFromGtkPreedit(text, attrs, cursor_position, + &composition_); + + // TODO(suzhe): due to a bug of webkit, we currently can't use selection range + // with composition string. See: https://bugs.webkit.org/show_bug.cgi?id=40805 + composition_.selection = ui::Range(cursor_position); + + // In case we are using a buggy input method which doesn't fire + // "preedit_start" signal. + if (composition_.text.length()) + is_composing_text_ = true; + + // Nothing needs to do, if it's currently in ProcessKeyEvent() + // handler, which will send preedit text to webkit later. + // Otherwise, we need send it here if it's been changed. + if (!is_in_key_event_handler_ && is_composing_text_ && + host_view_->GetRenderWidgetHost()) { + // Workaround http://crbug.com/45478 by sending fake key down/up events. + SendFakeCompositionKeyEvent(WebKit::WebInputEvent::RawKeyDown); + // TODO(suzhe): convert both renderer_host and renderer to use + // ui::CompositionText. + const std::vector<WebKit::WebCompositionUnderline>& underlines = + reinterpret_cast<const std::vector<WebKit::WebCompositionUnderline>&>( + composition_.underlines); + host_view_->GetRenderWidgetHost()->ImeSetComposition( + composition_.text, underlines, composition_.selection.start(), + composition_.selection.end()); + SendFakeCompositionKeyEvent(WebKit::WebInputEvent::KeyUp); + } +} + +void GtkIMContextWrapper::HandlePreeditEnd() { + if (composition_.text.length()) { + // The composition session has been finished. + composition_.Clear(); + is_composition_changed_ = true; + + // If there is still a preedit text when firing "preedit-end" signal, + // we need inform webkit to clear it. + // It's only necessary when it's not in ProcessKeyEvent (). + if (!is_in_key_event_handler_ && host_view_->GetRenderWidgetHost()) + host_view_->GetRenderWidgetHost()->ImeCancelComposition(); + } + + // Don't set is_composing_text_ to false here, because "preedit_end" + // signal may be fired before "commit" signal. +} + +void GtkIMContextWrapper::HandleHostViewRealize(GtkWidget* widget) { + // We should only set im context's client window once, because when setting + // client window.im context may destroy and recreate its internal states and + // objects. + if (widget->window) { + gtk_im_context_set_client_window(context_, widget->window); + gtk_im_context_set_client_window(context_simple_, widget->window); + } +} + +void GtkIMContextWrapper::HandleHostViewUnrealize() { + gtk_im_context_set_client_window(context_, NULL); + gtk_im_context_set_client_window(context_simple_, NULL); +} + +void GtkIMContextWrapper::SendFakeCompositionKeyEvent( + WebKit::WebInputEvent::Type type) { + NativeWebKeyboardEvent fake_event; + fake_event.windowsKeyCode = kCompositionEventKeyCode; + fake_event.skip_in_browser = true; + fake_event.type = type; + host_view_->ForwardKeyboardEvent(fake_event); +} + +void GtkIMContextWrapper::HandleCommitThunk( + GtkIMContext* context, gchar* text, GtkIMContextWrapper* self) { + self->HandleCommit(UTF8ToUTF16(text)); +} + +void GtkIMContextWrapper::HandlePreeditStartThunk( + GtkIMContext* context, GtkIMContextWrapper* self) { + self->HandlePreeditStart(); +} + +void GtkIMContextWrapper::HandlePreeditChangedThunk( + GtkIMContext* context, GtkIMContextWrapper* self) { + gchar* text = NULL; + PangoAttrList* attrs = NULL; + gint cursor_position = 0; + gtk_im_context_get_preedit_string(context, &text, &attrs, &cursor_position); + self->HandlePreeditChanged(text, attrs, cursor_position); + g_free(text); + pango_attr_list_unref(attrs); +} + +void GtkIMContextWrapper::HandlePreeditEndThunk( + GtkIMContext* context, GtkIMContextWrapper* self) { + self->HandlePreeditEnd(); +} + +void GtkIMContextWrapper::HandleHostViewRealizeThunk( + GtkWidget* widget, GtkIMContextWrapper* self) { + self->HandleHostViewRealize(widget); +} + +void GtkIMContextWrapper::HandleHostViewUnrealizeThunk( + GtkWidget* widget, GtkIMContextWrapper* self) { + self->HandleHostViewUnrealize(); +} diff --git a/content/browser/renderer_host/gtk_im_context_wrapper.h b/content/browser/renderer_host/gtk_im_context_wrapper.h new file mode 100644 index 0000000..a783296 --- /dev/null +++ b/content/browser/renderer_host/gtk_im_context_wrapper.h @@ -0,0 +1,197 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_RENDERER_HOST_GTK_IM_CONTEXT_WRAPPER_H_ +#define CONTENT_BROWSER_RENDERER_HOST_GTK_IM_CONTEXT_WRAPPER_H_ +#pragma once + +#include <gdk/gdk.h> +#include <pango/pango-attributes.h> +#include <vector> + +#include "base/basictypes.h" +#include "base/gtest_prod_util.h" +#include "base/string16.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebInputEvent.h" +#include "ui/base/ime/composition_text.h" +#include "ui/base/ime/text_input_type.h" + +namespace gfx { +class Rect; +} + +class RenderWidgetHostViewGtk; +struct NativeWebKeyboardEvent; +typedef struct _GtkIMContext GtkIMContext; +typedef struct _GtkWidget GtkWidget; + +// This class is a convenience wrapper for GtkIMContext. +// It creates and manages two GtkIMContext instances, one is GtkIMMulticontext, +// for plain text input box, another is GtkIMContextSimple, for password input +// box. +// +// This class is in charge of dispatching key events to these two GtkIMContext +// instances and handling signals emitted by them. Key events then will be +// forwarded to renderer along with input method results via corresponding host +// view. +// +// This class is used solely by RenderWidgetHostViewGtk. +class GtkIMContextWrapper { + public: + explicit GtkIMContextWrapper(RenderWidgetHostViewGtk* host_view); + ~GtkIMContextWrapper(); + + // Processes a gdk key event received by |host_view|. + void ProcessKeyEvent(GdkEventKey* event); + + void UpdateInputMethodState(ui::TextInputType type, + bool can_compose_inline, + const gfx::Rect& caret_rect); + void OnFocusIn(); + void OnFocusOut(); + bool is_focused() const { return is_focused_; } + +#if !defined(TOOLKIT_VIEWS) + // Not defined for views because the views context menu doesn't + // implement input methods yet. + GtkWidget* BuildInputMethodsGtkMenu(); +#endif + + void CancelComposition(); + + void ConfirmComposition(); + + private: + // Check if a text needs commit by forwarding a char event instead of + // by confirming as a composition text. + bool NeedCommitByForwardingCharEvent() const; + + // Check if the input method returned any result, eg. preedit and commit text. + bool HasInputMethodResult() const; + + void ProcessFilteredKeyPressEvent(NativeWebKeyboardEvent* wke); + void ProcessUnfilteredKeyPressEvent(NativeWebKeyboardEvent* wke); + + // Processes result returned from input method after filtering a key event. + // |filtered| indicates if the key event was filtered by the input method. + void ProcessInputMethodResult(const GdkEventKey* event, bool filtered); + + // Real code of "commit" signal handler. + void HandleCommit(const string16& text); + + // Real code of "preedit-start" signal handler. + void HandlePreeditStart(); + + // Real code of "preedit-changed" signal handler. + void HandlePreeditChanged(const gchar* text, + PangoAttrList* attrs, + int cursor_position); + + // Real code of "preedit-end" signal handler. + void HandlePreeditEnd(); + + // Real code of "realize" signal handler, used for setting im context's client + // window. + void HandleHostViewRealize(GtkWidget* widget); + + // Real code of "unrealize" signal handler, used for unsetting im context's + // client window. + void HandleHostViewUnrealize(); + + // Sends a fake composition key event with specified event type. A composition + // key event is a key event with special key code 229. + void SendFakeCompositionKeyEvent(WebKit::WebInputEvent::Type type); + + // Signal handlers of GtkIMContext object. + static void HandleCommitThunk(GtkIMContext* context, gchar* text, + GtkIMContextWrapper* self); + static void HandlePreeditStartThunk(GtkIMContext* context, + GtkIMContextWrapper* self); + static void HandlePreeditChangedThunk(GtkIMContext* context, + GtkIMContextWrapper* self); + static void HandlePreeditEndThunk(GtkIMContext* context, + GtkIMContextWrapper* self); + + // Signal handlers connecting to |host_view_|'s native view widget. + static void HandleHostViewRealizeThunk(GtkWidget* widget, + GtkIMContextWrapper* self); + static void HandleHostViewUnrealizeThunk(GtkWidget* widget, + GtkIMContextWrapper* self); + + // The parent object. + RenderWidgetHostViewGtk* host_view_; + + // The GtkIMContext object. + // In terms of the DOM event specification Appendix A + // <http://www.w3.org/TR/DOM-Level-3-Events/keyset.html>, + // GTK uses a GtkIMContext object for the following two purposes: + // 1. Composing Latin characters (A.1.2), and; + // 2. Composing CJK characters with an IME (A.1.3). + // Many JavaScript pages assume composed Latin characters are dispatched to + // their onkeypress() handlers but not dispatched CJK characters composed + // with an IME. To emulate this behavior, we should monitor the status of + // this GtkIMContext object and prevent sending Char events when a + // GtkIMContext object sends a "commit" signal with the CJK characters + // composed by an IME. + GtkIMContext* context_; + + // A GtkIMContextSimple object, for supporting dead/compose keys when input + // method is disabled, eg. in password input box. + GtkIMContext* context_simple_; + + // Whether or not this widget is focused. + bool is_focused_; + + // Whether or not the above GtkIMContext is composing a text with an IME. + // This flag is used in "commit" signal handler of the GtkIMContext object, + // which determines how to submit the result text to WebKit according to this + // flag. + // If this flag is true or there are more than one characters in the result, + // then the result text will be committed to WebKit as a confirmed + // composition. Otherwise, it'll be forwarded as a key event. + // + // The GtkIMContext object sends a "preedit_start" before it starts composing + // a text and a "preedit_end" signal after it finishes composing it. + // "preedit_start" signal is monitored to turn it on. + // We don't monitor "preedit_end" signal to turn it off, because an input + // method may fire "preedit_end" signal before "commit" signal. + // A buggy input method may not fire "preedit_start" and/or "preedit_end" + // at all, so this flag will also be set to true when "preedit_changed" signal + // is fired with non-empty preedit text. + bool is_composing_text_; + + // Whether or not the IME is enabled. + bool is_enabled_; + + // Whether or not it's currently running inside key event handler. + // If it's true, then preedit-changed and commit handler will backup the + // preedit or commit text instead of sending them down to webkit. + // key event handler will send them later. + bool is_in_key_event_handler_; + + // The most recent composition text information retrieved from context_; + ui::CompositionText composition_; + + // Whether or not the composition has been changed since last key event. + bool is_composition_changed_; + + // Stores a copy of the most recent commit text received by commit signal + // handler. + string16 commit_text_; + + // If it's true then the next "commit" signal will be suppressed. + // It's only used to workaround http://crbug.com/50485. + // TODO(suzhe): Remove it after input methods get fixed. + bool suppress_next_commit_; + + // Information of the last key event, for working around + // http://crosbug.com/6582 + int last_key_code_; + bool last_key_was_up_; + bool last_key_filtered_no_result_; + + DISALLOW_COPY_AND_ASSIGN(GtkIMContextWrapper); +}; + +#endif // CONTENT_BROWSER_RENDERER_HOST_GTK_IM_CONTEXT_WRAPPER_H_ diff --git a/content/browser/renderer_host/gtk_key_bindings_handler.cc b/content/browser/renderer_host/gtk_key_bindings_handler.cc new file mode 100644 index 0000000..94e3234 --- /dev/null +++ b/content/browser/renderer_host/gtk_key_bindings_handler.cc @@ -0,0 +1,325 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/renderer_host/gtk_key_bindings_handler.h" + +#include <gdk/gdkkeysyms.h> + +#include <string> + +#include "base/logging.h" +#include "base/string_util.h" +#include "content/common/native_web_keyboard_event.h" + +GtkKeyBindingsHandler::GtkKeyBindingsHandler(GtkWidget* parent_widget) + : handler_(CreateNewHandler()) { + DCHECK(GTK_IS_FIXED(parent_widget)); + // We need add the |handler_| object into gtk widget hierarchy, so that + // gtk_bindings_activate_event() can find correct display and keymaps from + // the |handler_| object. + gtk_fixed_put(GTK_FIXED(parent_widget), handler_.get(), -1, -1); +} + +GtkKeyBindingsHandler::~GtkKeyBindingsHandler() { + handler_.Destroy(); +} + +bool GtkKeyBindingsHandler::Match(const NativeWebKeyboardEvent& wke, + EditCommands* edit_commands) { + if (wke.type == WebKit::WebInputEvent::Char || !wke.os_event) + return false; + + edit_commands_.clear(); + // If this key event matches a predefined key binding, corresponding signal + // will be emitted. + gtk_bindings_activate_event(GTK_OBJECT(handler_.get()), wke.os_event); + + bool matched = !edit_commands_.empty(); + if (edit_commands) + edit_commands->swap(edit_commands_); + return matched; +} + +GtkWidget* GtkKeyBindingsHandler::CreateNewHandler() { + Handler* handler = + static_cast<Handler*>(g_object_new(HandlerGetType(), NULL)); + + handler->owner = this; + + // We don't need to show the |handler| object on screen, so set its size to + // zero. + gtk_widget_set_size_request(GTK_WIDGET(handler), 0, 0); + + // Prevents it from handling any events by itself. + gtk_widget_set_sensitive(GTK_WIDGET(handler), FALSE); + gtk_widget_set_events(GTK_WIDGET(handler), 0); + gtk_widget_set_can_focus(GTK_WIDGET(handler), TRUE); + +#if !GTK_CHECK_VERSION(2, 14, 0) + // "move-focus", "move-viewport", "select-all" and "toggle-cursor-visible" + // have no corresponding virtual methods. Prior to glib 2.18 (gtk 2.14), + // there is no way to override the default class handler of a signal. + // So we need hook these signal explicitly. + g_signal_connect(handler, "move-focus", G_CALLBACK(MoveFocus), NULL); + g_signal_connect(handler, "move-viewport", G_CALLBACK(MoveViewport), NULL); + g_signal_connect(handler, "select-all", G_CALLBACK(SelectAll), NULL); + g_signal_connect(handler, "toggle-cursor-visible", + G_CALLBACK(ToggleCursorVisible), NULL); +#endif + return GTK_WIDGET(handler); +} + +void GtkKeyBindingsHandler::EditCommandMatched( + const std::string& name, const std::string& value) { + edit_commands_.push_back(EditCommand(name, value)); +} + +void GtkKeyBindingsHandler::HandlerInit(Handler *self) { + self->owner = NULL; +} + +void GtkKeyBindingsHandler::HandlerClassInit(HandlerClass *klass) { + GtkTextViewClass* text_view_class = GTK_TEXT_VIEW_CLASS(klass); + GtkWidgetClass* widget_class = GTK_WIDGET_CLASS(klass); + + // Overrides all virtual methods related to editor key bindings. + text_view_class->backspace = BackSpace; + text_view_class->copy_clipboard = CopyClipboard; + text_view_class->cut_clipboard = CutClipboard; + text_view_class->delete_from_cursor = DeleteFromCursor; + text_view_class->insert_at_cursor = InsertAtCursor; + text_view_class->move_cursor = MoveCursor; + text_view_class->paste_clipboard = PasteClipboard; + text_view_class->set_anchor = SetAnchor; + text_view_class->toggle_overwrite = ToggleOverwrite; + widget_class->show_help = ShowHelp; + +#if GTK_CHECK_VERSION(2, 14, 0) + // "move-focus", "move-viewport", "select-all" and "toggle-cursor-visible" + // have no corresponding virtual methods. Since glib 2.18 (gtk 2.14), + // g_signal_override_class_handler() is introduced to override a signal + // handler. + g_signal_override_class_handler("move-focus", + G_TYPE_FROM_CLASS(klass), + G_CALLBACK(MoveFocus)); + + g_signal_override_class_handler("move-viewport", + G_TYPE_FROM_CLASS(klass), + G_CALLBACK(MoveViewport)); + + g_signal_override_class_handler("select-all", + G_TYPE_FROM_CLASS(klass), + G_CALLBACK(SelectAll)); + + g_signal_override_class_handler("toggle-cursor-visible", + G_TYPE_FROM_CLASS(klass), + G_CALLBACK(ToggleCursorVisible)); +#endif +} + +GType GtkKeyBindingsHandler::HandlerGetType() { + static volatile gsize type_id_volatile = 0; + if (g_once_init_enter(&type_id_volatile)) { + GType type_id = g_type_register_static_simple( + GTK_TYPE_TEXT_VIEW, + g_intern_static_string("GtkKeyBindingsHandler"), + sizeof(HandlerClass), + reinterpret_cast<GClassInitFunc>(HandlerClassInit), + sizeof(Handler), + reinterpret_cast<GInstanceInitFunc>(HandlerInit), + static_cast<GTypeFlags>(0)); + g_once_init_leave(&type_id_volatile, type_id); + } + return type_id_volatile; +} + +GtkKeyBindingsHandler* GtkKeyBindingsHandler::GetHandlerOwner( + GtkTextView* text_view) { + Handler* handler = G_TYPE_CHECK_INSTANCE_CAST( + text_view, HandlerGetType(), Handler); + DCHECK(handler); + return handler->owner; +} + +void GtkKeyBindingsHandler::BackSpace(GtkTextView* text_view) { + GetHandlerOwner(text_view)->EditCommandMatched("DeleteBackward", ""); +} + +void GtkKeyBindingsHandler::CopyClipboard(GtkTextView* text_view) { + GetHandlerOwner(text_view)->EditCommandMatched("Copy", ""); +} + +void GtkKeyBindingsHandler::CutClipboard(GtkTextView* text_view) { + GetHandlerOwner(text_view)->EditCommandMatched("Cut", ""); +} + +void GtkKeyBindingsHandler::DeleteFromCursor( + GtkTextView* text_view, GtkDeleteType type, gint count) { + if (!count) + return; + + const char *commands[3] = { NULL, NULL, NULL }; + switch (type) { + case GTK_DELETE_CHARS: + commands[0] = (count > 0 ? "DeleteForward" : "DeleteBackward"); + break; + case GTK_DELETE_WORD_ENDS: + commands[0] = (count > 0 ? "DeleteWordForward" : "DeleteWordBackward"); + break; + case GTK_DELETE_WORDS: + if (count > 0) { + commands[0] = "MoveWordForward"; + commands[1] = "DeleteWordBackward"; + } else { + commands[0] = "MoveWordBackward"; + commands[1] = "DeleteWordForward"; + } + break; + case GTK_DELETE_DISPLAY_LINES: + commands[0] = "MoveToBeginningOfLine"; + commands[1] = "DeleteToEndOfLine"; + break; + case GTK_DELETE_DISPLAY_LINE_ENDS: + commands[0] = (count > 0 ? "DeleteToEndOfLine" : + "DeleteToBeginningOfLine"); + break; + case GTK_DELETE_PARAGRAPH_ENDS: + commands[0] = (count > 0 ? "DeleteToEndOfParagraph" : + "DeleteToBeginningOfParagraph"); + break; + case GTK_DELETE_PARAGRAPHS: + commands[0] = "MoveToBeginningOfParagraph"; + commands[1] = "DeleteToEndOfParagraph"; + break; + default: + // GTK_DELETE_WHITESPACE has no corresponding editor command. + return; + } + + GtkKeyBindingsHandler* owner = GetHandlerOwner(text_view); + if (count < 0) + count = -count; + for (; count > 0; --count) { + for (const char* const* p = commands; *p; ++p) + owner->EditCommandMatched(*p, ""); + } +} + +void GtkKeyBindingsHandler::InsertAtCursor(GtkTextView* text_view, + const gchar* str) { + if (str && *str) + GetHandlerOwner(text_view)->EditCommandMatched("InsertText", str); +} + +void GtkKeyBindingsHandler::MoveCursor( + GtkTextView* text_view, GtkMovementStep step, gint count, + gboolean extend_selection) { + if (!count) + return; + + std::string command; + switch (step) { + case GTK_MOVEMENT_LOGICAL_POSITIONS: + command = (count > 0 ? "MoveForward" : "MoveBackward"); + break; + case GTK_MOVEMENT_VISUAL_POSITIONS: + command = (count > 0 ? "MoveRight" : "MoveLeft"); + break; + case GTK_MOVEMENT_WORDS: + command = (count > 0 ? "MoveWordForward" : "MoveWordBackward"); + break; + case GTK_MOVEMENT_DISPLAY_LINES: + command = (count > 0 ? "MoveDown" : "MoveUp"); + break; + case GTK_MOVEMENT_DISPLAY_LINE_ENDS: + command = (count > 0 ? "MoveToEndOfLine" : "MoveToBeginningOfLine"); + break; + case GTK_MOVEMENT_PARAGRAPH_ENDS: + command = (count > 0 ? "MoveToEndOfParagraph" : + "MoveToBeginningOfParagraph"); + break; + case GTK_MOVEMENT_PAGES: + command = (count > 0 ? "MovePageDown" : "MovePageUp"); + break; + case GTK_MOVEMENT_BUFFER_ENDS: + command = (count > 0 ? "MoveToEndOfDocument" : + "MoveToBeginningOfDocument"); + break; + default: + // GTK_MOVEMENT_PARAGRAPHS and GTK_MOVEMENT_HORIZONTAL_PAGES have + // no corresponding editor commands. + return; + } + + GtkKeyBindingsHandler* owner = GetHandlerOwner(text_view); + if (extend_selection) + command.append("AndModifySelection"); + if (count < 0) + count = -count; + for (; count > 0; --count) + owner->EditCommandMatched(command, ""); +} + +void GtkKeyBindingsHandler::MoveViewport( + GtkTextView* text_view, GtkScrollStep step, gint count) { + // Not supported by webkit. +#if !GTK_CHECK_VERSION(2, 14, 0) + // Before gtk 2.14.0, there is no way to override a non-virtual default signal + // handler, so we need stop the signal emission explicitly to prevent the + // default handler from being executed. + g_signal_stop_emission_by_name(text_view, "move-viewport"); +#endif +} + +void GtkKeyBindingsHandler::PasteClipboard(GtkTextView* text_view) { + GetHandlerOwner(text_view)->EditCommandMatched("Paste", ""); +} + +void GtkKeyBindingsHandler::SelectAll(GtkTextView* text_view, gboolean select) { + if (select) + GetHandlerOwner(text_view)->EditCommandMatched("SelectAll", ""); + else + GetHandlerOwner(text_view)->EditCommandMatched("Unselect", ""); +#if !GTK_CHECK_VERSION(2, 14, 0) + // Before gtk 2.14.0, there is no way to override a non-virtual default signal + // handler, so we need stop the signal emission explicitly to prevent the + // default handler from being executed. + g_signal_stop_emission_by_name(text_view, "select-all"); +#endif +} + +void GtkKeyBindingsHandler::SetAnchor(GtkTextView* text_view) { + GetHandlerOwner(text_view)->EditCommandMatched("SetMark", ""); +} + +void GtkKeyBindingsHandler::ToggleCursorVisible(GtkTextView* text_view) { + // Not supported by webkit. +#if !GTK_CHECK_VERSION(2, 14, 0) + // Before gtk 2.14.0, there is no way to override a non-virtual default signal + // handler, so we need stop the signal emission explicitly to prevent the + // default handler from being executed. + g_signal_stop_emission_by_name(text_view, "toggle-cursor-visible"); +#endif +} + +void GtkKeyBindingsHandler::ToggleOverwrite(GtkTextView* text_view) { + // Not supported by webkit. +} + +gboolean GtkKeyBindingsHandler::ShowHelp(GtkWidget* widget, + GtkWidgetHelpType arg1) { + // Just for disabling the default handler. + return FALSE; +} + +void GtkKeyBindingsHandler::MoveFocus(GtkWidget* widget, + GtkDirectionType arg1) { + // Just for disabling the default handler. +#if !GTK_CHECK_VERSION(2, 14, 0) + // Before gtk 2.14.0, there is no way to override a non-virtual default signal + // handler, so we need stop the signal emission explicitly to prevent the + // default handler from being executed. + g_signal_stop_emission_by_name(widget, "move-focus"); +#endif +} diff --git a/content/browser/renderer_host/gtk_key_bindings_handler.h b/content/browser/renderer_host/gtk_key_bindings_handler.h new file mode 100644 index 0000000..071a3b1 --- /dev/null +++ b/content/browser/renderer_host/gtk_key_bindings_handler.h @@ -0,0 +1,128 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_RENDERER_HOST_GTK_KEY_BINDINGS_HANDLER_H_ +#define CONTENT_BROWSER_RENDERER_HOST_GTK_KEY_BINDINGS_HANDLER_H_ +#pragma once + +#include <gtk/gtk.h> + +#include <string> + +#include "content/common/edit_command.h" +#include "ui/base/gtk/owned_widget_gtk.h" + +struct NativeWebKeyboardEvent; + +// This class is a convenience class for handling editor key bindings defined +// in gtk keyboard theme. +// In gtk, only GtkEntry and GtkTextView support customizing editor key bindings +// through keyboard theme. And in gtk keyboard theme definition file, each key +// binding must be bound to a specific class or object. So existing keyboard +// themes only define editor key bindings exactly for GtkEntry and GtkTextView. +// Then, the only way for us to intercept editor key bindings defined in +// keyboard theme, is to create a GtkEntry or GtkTextView object and call +// gtk_bindings_activate_event() against it for the key events. If a key event +// matches a predefined key binding, corresponding signal will be emitted. +// GtkTextView is used here because it supports more key bindings than GtkEntry, +// but in order to minimize the side effect of using a GtkTextView object, a new +// class derived from GtkTextView is used, which overrides all signals related +// to key bindings, to make sure GtkTextView won't receive them. +// +// See third_party/WebKit/Source/WebCore/editing/EditorCommand.cpp for detailed +// definition of webkit edit commands. +// See webkit/glue/editor_client_impl.cc for key bindings predefined in our +// webkit glue. +class GtkKeyBindingsHandler { + public: + explicit GtkKeyBindingsHandler(GtkWidget* parent_widget); + ~GtkKeyBindingsHandler(); + + // Matches a key event against predefined gtk key bindings, false will be + // returned if the key event doesn't correspond to a predefined key binding. + // Edit commands matched with |wke| will be stored in |edit_commands|. + bool Match(const NativeWebKeyboardEvent& wke, EditCommands* edit_commands); + + private: + // Object structure of Handler class, which is derived from GtkTextView. + struct Handler { + GtkTextView parent_object; + GtkKeyBindingsHandler *owner; + }; + + // Class structure of Handler class. + struct HandlerClass { + GtkTextViewClass parent_class; + }; + + // Creates a new instance of Handler class. + GtkWidget* CreateNewHandler(); + + // Adds an edit command to the key event. + void EditCommandMatched(const std::string& name, const std::string& value); + + // Initializes Handler structure. + static void HandlerInit(Handler *self); + + // Initializes HandlerClass structure. + static void HandlerClassInit(HandlerClass *klass); + + // Registeres Handler class to GObject type system and return its type id. + static GType HandlerGetType(); + + // Gets the GtkKeyBindingsHandler object which owns the Handler object. + static GtkKeyBindingsHandler* GetHandlerOwner(GtkTextView* text_view); + + // Handler of "backspace" signal. + static void BackSpace(GtkTextView* text_view); + + // Handler of "copy-clipboard" signal. + static void CopyClipboard(GtkTextView* text_view); + + // Handler of "cut-clipboard" signal. + static void CutClipboard(GtkTextView* text_view); + + // Handler of "delete-from-cursor" signal. + static void DeleteFromCursor(GtkTextView* text_view, GtkDeleteType type, + gint count); + + // Handler of "insert-at-cursor" signal. + static void InsertAtCursor(GtkTextView* text_view, const gchar* str); + + // Handler of "move-cursor" signal. + static void MoveCursor(GtkTextView* text_view, GtkMovementStep step, + gint count, gboolean extend_selection); + + // Handler of "move-viewport" signal. + static void MoveViewport(GtkTextView* text_view, GtkScrollStep step, + gint count); + + // Handler of "paste-clipboard" signal. + static void PasteClipboard(GtkTextView* text_view); + + // Handler of "select-all" signal. + static void SelectAll(GtkTextView* text_view, gboolean select); + + // Handler of "set-anchor" signal. + static void SetAnchor(GtkTextView* text_view); + + // Handler of "toggle-cursor-visible" signal. + static void ToggleCursorVisible(GtkTextView* text_view); + + // Handler of "toggle-overwrite" signal. + static void ToggleOverwrite(GtkTextView* text_view); + + // Handler of "show-help" signal. + static gboolean ShowHelp(GtkWidget* widget, GtkWidgetHelpType arg1); + + // Handler of "move-focus" signal. + static void MoveFocus(GtkWidget* widget, GtkDirectionType arg1); + + OwnedWidgetGtk handler_; + + // Buffer to store the match results. + EditCommands edit_commands_; +}; + +#endif // CONTENT_BROWSER_RENDERER_HOST_GTK_KEY_BINDINGS_HANDLER_H_ diff --git a/content/browser/renderer_host/gtk_key_bindings_handler_unittest.cc b/content/browser/renderer_host/gtk_key_bindings_handler_unittest.cc new file mode 100644 index 0000000..10c824f --- /dev/null +++ b/content/browser/renderer_host/gtk_key_bindings_handler_unittest.cc @@ -0,0 +1,222 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/renderer_host/gtk_key_bindings_handler.h" + +#include <gdk/gdkkeysyms.h> +#include <string> +#include <utility> +#include <vector> + +#include "base/basictypes.h" +#include "base/file_util.h" +#include "base/logging.h" +#include "base/path_service.h" +#include "base/string_util.h" +#include "content/common/content_paths.h" +#include "content/common/edit_command.h" +#include "content/common/native_web_keyboard_event.h" +#include "testing/gtest/include/gtest/gtest.h" + +class GtkKeyBindingsHandlerTest : public testing::Test { + protected: + struct EditCommand { + const char* name; + const char* value; + }; + + GtkKeyBindingsHandlerTest() + : window_(gtk_window_new(GTK_WINDOW_TOPLEVEL)), + handler_(NULL) { + FilePath gtkrc; + PathService::Get(content::DIR_TEST_DATA, >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..c857651 --- /dev/null +++ b/content/browser/renderer_host/render_widget_host_view_gtk.h @@ -0,0 +1,273 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_RENDERER_HOST_RENDER_WIDGET_HOST_VIEW_GTK_H_ +#define CONTENT_BROWSER_RENDERER_HOST_RENDER_WIDGET_HOST_VIEW_GTK_H_ +#pragma once + +#include <gdk/gdk.h> + +#include <string> +#include <vector> + +#include "base/memory/scoped_ptr.h" +#include "base/time.h" +#include "content/browser/renderer_host/render_widget_host_view.h" +#include "ui/base/animation/animation_delegate.h" +#include "ui/base/animation/slide_animation.h" +#include "ui/base/gtk/gtk_signal.h" +#include "ui/base/gtk/owned_widget_gtk.h" +#include "ui/gfx/native_widget_types.h" +#include "ui/gfx/rect.h" +#include "webkit/glue/webcursor.h" +#include "webkit/plugins/npapi/gtk_plugin_container_manager.h" + +class RenderWidgetHost; +class GtkIMContextWrapper; +struct NativeWebKeyboardEvent; + +#if defined(OS_CHROMEOS) +namespace views { +class TooltipWindowGtk; +} +#else +class GtkKeyBindingsHandler; +#endif // defined(OS_CHROMEOS) + +typedef struct _GtkClipboard GtkClipboard; +typedef struct _GtkSelectionData GtkSelectionData; + +// ----------------------------------------------------------------------------- +// See comments in render_widget_host_view.h about this class and its members. +// ----------------------------------------------------------------------------- +class RenderWidgetHostViewGtk : public RenderWidgetHostView, + public ui::AnimationDelegate { + public: + explicit RenderWidgetHostViewGtk(RenderWidgetHost* widget); + virtual ~RenderWidgetHostViewGtk(); + + // Initialize this object for use as a drawing area. + void InitAsChild(); + + // RenderWidgetHostView implementation. + virtual void InitAsPopup(RenderWidgetHostView* parent_host_view, + const gfx::Rect& pos) OVERRIDE; + virtual void InitAsFullscreen( + RenderWidgetHostView* reference_host_view) OVERRIDE; + virtual RenderWidgetHost* GetRenderWidgetHost() const OVERRIDE; + virtual void DidBecomeSelected() OVERRIDE; + virtual void WasHidden() OVERRIDE; + virtual void SetSize(const gfx::Size& size) OVERRIDE; + virtual void SetBounds(const gfx::Rect& rect) OVERRIDE; + virtual gfx::NativeView GetNativeView() OVERRIDE; + virtual void MovePluginWindows( + const std::vector<webkit::npapi::WebPluginGeometry>& moves) OVERRIDE; + virtual void Focus() OVERRIDE; + virtual void Blur() OVERRIDE; + virtual bool HasFocus() OVERRIDE; + virtual void Show() OVERRIDE; + virtual void Hide() OVERRIDE; + virtual bool IsShowing() OVERRIDE; + virtual gfx::Rect GetViewBounds() const OVERRIDE; + virtual void UpdateCursor(const WebCursor& cursor) OVERRIDE; + virtual void SetIsLoading(bool is_loading) OVERRIDE; + virtual void ImeUpdateTextInputState(ui::TextInputType type, + bool can_compose_inline, + const gfx::Rect& caret_rect) OVERRIDE; + virtual void ImeCancelComposition() OVERRIDE; + virtual void DidUpdateBackingStore( + const gfx::Rect& scroll_rect, int scroll_dx, int scroll_dy, + const std::vector<gfx::Rect>& copy_rects) OVERRIDE; + virtual void RenderViewGone(base::TerminationStatus status, + int error_code) OVERRIDE; + virtual void Destroy() OVERRIDE; + virtual void WillDestroyRenderWidget(RenderWidgetHost* rwh) {} + virtual void SetTooltipText(const std::wstring& tooltip_text) OVERRIDE; + virtual void SelectionChanged(const std::string& text, + const ui::Range& range, + const gfx::Point& start, + const gfx::Point& end) OVERRIDE; + virtual void ShowingContextMenu(bool showing) OVERRIDE; + virtual BackingStore* AllocBackingStore(const gfx::Size& size) OVERRIDE; + virtual void SetBackground(const SkBitmap& background) OVERRIDE; + virtual void CreatePluginContainer(gfx::PluginWindowHandle id) OVERRIDE; + virtual void DestroyPluginContainer(gfx::PluginWindowHandle id) OVERRIDE; + virtual void SetVisuallyDeemphasized(const SkColor* color, + bool animate) OVERRIDE; + virtual void UnhandledWheelEvent( + const WebKit::WebMouseWheelEvent& event) OVERRIDE; + virtual void SetHasHorizontalScrollbar( + bool has_horizontal_scrollbar) OVERRIDE; + virtual void SetScrollOffsetPinning( + bool is_pinned_to_left, bool is_pinned_to_right) OVERRIDE; + virtual void AcceleratedCompositingActivated(bool activated) OVERRIDE; + virtual gfx::PluginWindowHandle GetCompositingSurface() OVERRIDE; + + // ui::AnimationDelegate implementation. + virtual void AnimationEnded(const ui::Animation* animation) OVERRIDE; + virtual void AnimationProgressed(const ui::Animation* animation) OVERRIDE; + virtual void AnimationCanceled(const ui::Animation* animation) OVERRIDE; + + gfx::NativeView native_view() const { return view_.get(); } + + // If the widget is aligned with an edge of the monitor its on and the user + // attempts to drag past that edge we track the number of times it has + // occurred, so that we can force the widget to scroll when it otherwise + // would be unable to. + void ModifyEventForEdgeDragging(GtkWidget* widget, GdkEventMotion* event); + void Paint(const gfx::Rect&); + + // Called by GtkIMContextWrapper to forward a keyboard event to renderer. + // On Linux (not ChromeOS): + // Before calling RenderWidgetHost::ForwardKeyboardEvent(), this method + // calls GtkKeyBindingsHandler::Match() against the event and send matched + // edit commands to renderer by calling + // RenderWidgetHost::ForwardEditCommandsForNextKeyEvent(). + void ForwardKeyboardEvent(const NativeWebKeyboardEvent& event); + + GdkEventButton* last_mouse_down() const { + return last_mouse_down_; + } + +#if !defined(TOOLKIT_VIEWS) + // Builds a submenu containing all the gtk input method commands. + GtkWidget* BuildInputMethodsGtkMenu(); +#endif + + private: + friend class RenderWidgetHostViewGtkWidget; + + CHROMEGTK_CALLBACK_1(RenderWidgetHostViewGtk, + gboolean, + OnWindowStateEvent, + GdkEventWindowState*); + + CHROMEGTK_CALLBACK_0(RenderWidgetHostViewGtk, + void, + OnDestroy); + + // Returns whether the widget needs an input grab (GTK+ and X) to work + // properly. + bool NeedsInputGrab(); + + // Returns whether this render view is a popup (<select> dropdown or + // autocomplete window). + bool IsPopup() const; + + // Do initialization needed by all InitAs*() methods. + void DoSharedInit(); + + // Do initialization needed just by InitAsPopup() and InitAsFullscreen(). + // We move and resize |window| to |bounds| and show it and its contents. + void DoPopupOrFullscreenInit(GtkWindow* window, const gfx::Rect& bounds); + + // Update the display cursor for the render view. + void ShowCurrentCursor(); + + void set_last_mouse_down(GdkEventButton* event); + + // The model object. + RenderWidgetHost* host_; + + // The native UI widget. + OwnedWidgetGtk view_; + + // This is true when we are currently painting and thus should handle extra + // paint requests by expanding the invalid rect rather than actually + // painting. + bool about_to_validate_and_paint_; + + // This is the rectangle which we'll paint. + gfx::Rect invalid_rect_; + + // Whether or not this widget is hidden. + bool is_hidden_; + + // Whether we are currently loading. + bool is_loading_; + + // The cursor for the page. This is passed up from the renderer. + WebCursor current_cursor_; + + // Whether we are showing a context menu. + bool is_showing_context_menu_; + + // The time at which this view started displaying white pixels as a result of + // not having anything to paint (empty backing store from renderer). This + // value returns true for is_null() if we are not recording whiteout times. + base::TimeTicks whiteout_start_time_; + + // The time it took after this view was selected for it to be fully painted. + base::TimeTicks tab_switch_paint_time_; + + // A color we use to shade the entire render view. If 100% transparent, we do + // not shade the render view. + SkColor overlay_color_; + + // The animation used for the abovementioned shade effect. The animation's + // value affects the alpha we use for |overlay_color_|. + ui::SlideAnimation overlay_animation_; + + // The native view of our parent widget. Used only for popups. + GtkWidget* parent_; + + // We ignore the first mouse release on popups so the popup will remain open. + bool is_popup_first_mouse_release_; + + // Whether or not this widget's input context was focused before being + // shadowed by another widget. Used in OnGrabNotify() handler to track the + // focused state correctly. + bool was_imcontext_focused_before_grab_; + + // True if we are responsible for creating an X grab. This will only be used + // for <select> dropdowns. It should be true for most such cases, but false + // for extension popups. + bool do_x_grab_; + + // Is the widget fullscreen? + bool is_fullscreen_; + + // For full-screen windows we have a OnDestroy handler that we need to remove, + // so we keep it ID here. + unsigned long destroy_handler_id_; + + // A convenience wrapper object for GtkIMContext; + scoped_ptr<GtkIMContextWrapper> im_context_; + +#if !defined(OS_CHROMEOS) + // A convenience object for handling editor key bindings defined in gtk + // keyboard theme. + scoped_ptr<GtkKeyBindingsHandler> key_bindings_handler_; +#endif + + // Helper class that lets us allocate plugin containers and move them. + webkit::npapi::GtkPluginContainerManager plugin_container_manager_; + + // The size that we want the renderer to be. We keep this in a separate + // variable because resizing in GTK+ is async. + gfx::Size requested_size_; + + // The number of times the user has dragged against horizontal edge of the + // monitor (if the widget is aligned with that edge). Negative values + // indicate the left edge, positive the right. + int dragged_at_horizontal_edge_; + + // The number of times the user has dragged against vertical edge of the + // monitor (if the widget is aligned with that edge). Negative values + // indicate the top edge, positive the bottom. + int dragged_at_vertical_edge_; + + gfx::PluginWindowHandle compositing_surface_; + + // The event for the last mouse down we handled. We need this for context + // menus and drags. + GdkEventButton* last_mouse_down_; + +#if defined(OS_CHROMEOS) + // Custimized tooltip window. + scoped_ptr<views::TooltipWindowGtk> tooltip_window_; +#endif // defined(OS_CHROMEOS) +}; + +#endif // CHROME_BROWSER_RENDERER_HOST_RENDER_WIDGET_HOST_VIEW_GTK_H_ diff --git a/content/common/content_paths.cc b/content/common/content_paths.cc index 4e9b32b..2664349 100644 --- a/content/common/content_paths.cc +++ b/content/common/content_paths.cc @@ -4,6 +4,7 @@ #include "content/common/content_paths.h" +#include "base/file_util.h" #include "base/path_service.h" namespace content { @@ -12,8 +13,22 @@ bool PathProvider(int key, FilePath* result) { switch (key) { case CHILD_PROCESS_EXE: return PathService::Get(base::FILE_EXE, result); - default: + case DIR_TEST_DATA: { + FilePath cur; + if (!PathService::Get(base::DIR_SOURCE_ROOT, &cur)) + return false; + cur = cur.Append(FILE_PATH_LITERAL("content")); + cur = cur.Append(FILE_PATH_LITERAL("test")); + cur = cur.Append(FILE_PATH_LITERAL("data")); + if (!file_util::PathExists(cur)) // we don't want to create this + return false; + + *result = cur; + return true; break; + } + default: + return false; } return false; diff --git a/content/common/content_paths.h b/content/common/content_paths.h index 49720a0..cdf07a7 100644 --- a/content/common/content_paths.h +++ b/content/common/content_paths.h @@ -17,6 +17,9 @@ enum { // Path and filename to the executable to use for child processes. CHILD_PROCESS_EXE = PATH_START, + // Valid only in development environment + DIR_TEST_DATA, + PATH_END }; diff --git a/content/common/content_switches.cc b/content/common/content_switches.cc index 20dca2a..4058d4e 100644 --- a/content/common/content_switches.cc +++ b/content/common/content_switches.cc @@ -471,6 +471,11 @@ const char kAuditHandles[] = "enable-handle-auditing"; const char kAuditAllHandles[] = "enable-handle-auditing-all"; #endif +#if defined(OS_POSIX) && !defined(OS_MACOSX) +// Specify the amount the trackpad should scroll by. +const char kScrollPixels[] = "scroll-pixels"; +#endif + #if !defined(OFFICIAL_BUILD) // Causes the renderer process to throw an assertion on launch. const char kRendererCheckFalseTest[] = "renderer-check-false-test"; diff --git a/content/common/content_switches.h b/content/common/content_switches.h index a61a35b..adc93dd 100644 --- a/content/common/content_switches.h +++ b/content/common/content_switches.h @@ -152,6 +152,10 @@ extern const char kAuditHandles[]; extern const char kAuditAllHandles[]; #endif +#if defined(OS_POSIX) && !defined(OS_MACOSX) +extern const char kScrollPixels[]; +#endif + #if !defined(OFFICIAL_BUILD) extern const char kRendererCheckFalseTest[]; #endif diff --git a/content/content_browser.gypi b/content/content_browser.gypi index c4ac49d..b96735b 100644 --- a/content/content_browser.gypi +++ b/content/content_browser.gypi @@ -322,6 +322,10 @@ 'browser/renderer_host/global_request_id.h', 'browser/renderer_host/gpu_message_filter.cc', 'browser/renderer_host/gpu_message_filter.h', + 'browser/renderer_host/gtk_im_context_wrapper.cc', + 'browser/renderer_host/gtk_im_context_wrapper.h', + 'browser/renderer_host/gtk_key_bindings_handler.cc', + 'browser/renderer_host/gtk_key_bindings_handler.h', 'browser/renderer_host/media/audio_common.cc', 'browser/renderer_host/media/audio_common.h', 'browser/renderer_host/media/audio_input_device_manager.cc', @@ -387,6 +391,8 @@ 'browser/renderer_host/render_widget_host_mac.cc', 'browser/renderer_host/render_widget_host_view.cc', 'browser/renderer_host/render_widget_host_view.h', + 'browser/renderer_host/render_widget_host_view_gtk.cc', + 'browser/renderer_host/render_widget_host_view_gtk.h', 'browser/renderer_host/resource_dispatcher_host.cc', 'browser/renderer_host/resource_dispatcher_host.h', 'browser/renderer_host/resource_dispatcher_host_delegate.h', @@ -566,6 +572,20 @@ '../sandbox/sandbox.gyp:sandbox', ], }], + ['chromeos==1', { + 'sources!': [ + 'browser/renderer_host/gtk_key_bindings_handler.cc', + 'browser/renderer_host/gtk_key_bindings_handler.h', + ], + }], + ['touchui==1', { + 'sources/': [ + ['exclude', '^browser/renderer_host/gtk_im_context_wrapper.cc'], + ['exclude', '^browser/renderer_host/gtk_im_context_wrapper.h'], + ['exclude', '^browser/renderer_host/render_widget_host_view_gtk.cc'], + ['exclude', '^browser/renderer_host/render_widget_host_view_gtk.h'], + ], + }], ], }, ], diff --git a/content/content_tests.gypi b/content/content_tests.gypi index 1c59e934..b06c848 100644 --- a/content/content_tests.gypi +++ b/content/content_tests.gypi @@ -50,6 +50,7 @@ 'sources': [ 'browser/browser_thread_unittest.cc', 'browser/child_process_security_policy_unittest.cc', + 'browser/renderer_host/gtk_key_bindings_handler_unittest.cc', 'browser/ssl/ssl_host_state_unittest.cc', 'browser/trace_subscriber_stdio_unittest.cc', 'common/process_watcher_unittest.cc', @@ -79,6 +80,11 @@ '../base/allocator/allocator.gyp:allocator', ], }], + ['chromeos==1', { + 'sources/': [ + ['exclude', '^browser/renderer_host/gtk_key_bindings_handler_unittest.cc'], + ], + }], ], }, ], diff --git a/content/test/content_test_suite.cc b/content/test/content_test_suite.cc index 2cfc1e5..a6ebf01 100644 --- a/content/test/content_test_suite.cc +++ b/content/test/content_test_suite.cc @@ -7,8 +7,10 @@ #include "base/logging.h" #include "base/memory/scoped_ptr.h" #include "content/common/content_client.h" +#include "content/common/content_paths.h" #include "content/test/test_content_client.h" #include "testing/gtest/include/gtest/gtest.h" +#include "ui/base/ui_base_paths.h" namespace { @@ -47,6 +49,9 @@ ContentTestSuite::~ContentTestSuite() { void ContentTestSuite::Initialize() { base::TestSuite::Initialize(); + content::RegisterPathProvider(); + ui::RegisterPathProvider(); + testing::TestEventListeners& listeners = testing::UnitTest::GetInstance()->listeners(); listeners.Append(new TestContentClientInitializer); diff --git a/content/test/data/gtk_key_bindings_test_gtkrc b/content/test/data/gtk_key_bindings_test_gtkrc new file mode 100644 index 0000000..812971c --- /dev/null +++ b/content/test/data/gtk_key_bindings_test_gtkrc @@ -0,0 +1,83 @@ +# A keybinding set for testing GtkKeyBindingsHandler. +# chrome/browser/render_host/gtk_key_bindings_handler_unittest.cc and this +# file must be kept in sync. +# This file covers all key bindings supported by GtkKeyBindingsHandler. + +binding "gtk-key-bindings-handler" +{ + # Test "move-cursor" + bind "<ctrl>1" { + "move-cursor" (logical-positions, -2, 0) + "move-cursor" (logical-positions, 2, 0) + "move-cursor" (visual-positions, -1, 1) + "move-cursor" (visual-positions, 1, 1) + "move-cursor" (words, -1, 0) + "move-cursor" (words, 1, 0) + "move-cursor" (display-lines, -1, 0) + "move-cursor" (display-lines, 1, 0) + "move-cursor" (display-line-ends, -1, 0) + "move-cursor" (display-line-ends, 1, 0) + "move-cursor" (paragraph-ends, -1, 0) + "move-cursor" (paragraph-ends, 1, 0) + "move-cursor" (pages, -1, 0) + "move-cursor" (pages, 1, 0) + "move-cursor" (buffer-ends, -1, 0) + "move-cursor" (buffer-ends, 1, 0) + } + + # Test "delete-from-cursor" + bind "<ctrl>2" { + "delete-from-cursor" (chars, -2) + "delete-from-cursor" (chars, 2) + "delete-from-cursor" (word-ends, -1) + "delete-from-cursor" (word-ends, 1) + "delete-from-cursor" (words, -1) + "delete-from-cursor" (words, 1) + "delete-from-cursor" (display-lines, -1) + "delete-from-cursor" (display-lines, 1) + "delete-from-cursor" (display-line-ends, -1) + "delete-from-cursor" (display-line-ends, 1) + "delete-from-cursor" (paragraph-ends, -1) + "delete-from-cursor" (paragraph-ends, 1) + "delete-from-cursor" (paragraphs, -1) + "delete-from-cursor" (paragraphs, 1) + } + + # Test backspace + bind "<ctrl>3" { + "backspace" () + } + + # Test copy-clipboard + bind "<ctrl>4" { + "copy-clipboard" () + } + + # Test cut-clipboard + bind "<ctrl>5" { + "cut-clipboard" () + } + + # Test insert-at-cursor + bind "<ctrl>6" { + "insert-at-cursor" ("hello") + } + + # Test paste-clipboard + bind "<ctrl>7" { + "paste-clipboard" () + } + + # Test select-all + bind "<ctrl>8" { + "select-all" (0) + "select-all" (1) + } + + # Test set-anchor + bind "<ctrl>9" { + "set-anchor" () + } +} + +class "GtkTextView" binding "gtk-key-bindings-handler" |