From 60507b1cedf0d19b1a5d9034abb2eb25a2432439 Mon Sep 17 00:00:00 2001 From: "estade@chromium.org" <estade@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> Date: Mon, 2 Nov 2009 23:51:35 +0000 Subject: [PATCH] Re-landing of r30726, which was reverted as a possible cause of chromeos buildbot failures Handle GTK enter and leave notification events and pass them to WebKit as mouse move events. This prevents an HTML widget from staying in the mouseover state when the cursor leaves the window. Add a new ui test that checks the specific case that was broken by warping the mouse pointer inside and outside the content area. BUG=24660 TEST=ui_tests patch by Dominic Mazzoni <dmazzoni [at] google> original review: http://codereview.chromium.org/274010/show git-svn-id: svn://svn.chromium.org/chrome/trunk/src@30771 0039d316-1c4b-4281-b951-d872f2087c98 --- .../browser/automation/automation_provider.cc | 8 ++ .../browser/automation/automation_provider.h | 3 + .../render_widget_host_view_gtk.cc | 18 +++- chrome/chrome.gyp | 1 + .../automation/automation_messages_internal.h | 6 ++ chrome/test/automation/window_proxy.cc | 7 ++ chrome/test/automation/window_proxy.h | 4 + chrome/test/data/mouseleave.html | 42 ++++++++ chrome/test/ui/mouseleave_uitest.cc | 99 +++++++++++++++++++ webkit/api/public/gtk/WebInputEventFactory.h | 2 + webkit/api/src/gtk/WebInputEventFactory.cpp | 36 +++++++ 11 files changed, 225 insertions(+), 1 deletion(-) create mode 100644 chrome/test/data/mouseleave.html create mode 100644 chrome/test/ui/mouseleave_uitest.cc diff --git a/chrome/browser/automation/automation_provider.cc b/chrome/browser/automation/automation_provider.cc index 63d2a452e8b9f..64b013382b2ee 100644 --- a/chrome/browser/automation/automation_provider.cc +++ b/chrome/browser/automation/automation_provider.cc @@ -303,6 +303,7 @@ void AutomationProvider::OnMessageReceived(const IPC::Message& message) { IPC_MESSAGE_HANDLER(AutomationMsg_SetWindowVisible, SetWindowVisible) #if !defined(OS_MACOSX) IPC_MESSAGE_HANDLER(AutomationMsg_WindowClick, WindowSimulateClick) + IPC_MESSAGE_HANDLER(AutomationMsg_WindowMouseMove, WindowSimulateMouseMove) IPC_MESSAGE_HANDLER(AutomationMsg_WindowKeyPress, WindowSimulateKeyPress) #endif // !defined(OS_MACOSX) #if defined(OS_WIN) || defined(OS_LINUX) @@ -845,6 +846,13 @@ void AutomationProvider::WindowSimulateClick(const IPC::Message& message, } } +void AutomationProvider::WindowSimulateMouseMove(const IPC::Message& message, + int handle, + const gfx::Point& location) { + if (window_tracker_->ContainsHandle(handle)) + ui_controls::SendMouseMove(location.x(), location.y()); +} + void AutomationProvider::WindowSimulateKeyPress(const IPC::Message& message, int handle, int key, diff --git a/chrome/browser/automation/automation_provider.h b/chrome/browser/automation/automation_provider.h index 65b7795554406..dc540da868574 100644 --- a/chrome/browser/automation/automation_provider.h +++ b/chrome/browser/automation/automation_provider.h @@ -173,6 +173,9 @@ class AutomationProvider : public base::RefCounted<AutomationProvider>, int handle, const gfx::Point& click, int flags); + void WindowSimulateMouseMove(const IPC::Message& message, + int handle, + const gfx::Point& location); void WindowSimulateKeyPress(const IPC::Message& message, int handle, int key, diff --git a/chrome/browser/renderer_host/render_widget_host_view_gtk.cc b/chrome/browser/renderer_host/render_widget_host_view_gtk.cc index 250e0ab3ba204..e89aef2cbe47d 100644 --- a/chrome/browser/renderer_host/render_widget_host_view_gtk.cc +++ b/chrome/browser/renderer_host/render_widget_host_view_gtk.cc @@ -60,7 +60,10 @@ class RenderWidgetHostViewGtkWidget { GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_KEY_PRESS_MASK | - GDK_KEY_RELEASE_MASK); + GDK_KEY_RELEASE_MASK | + GDK_FOCUS_CHANGE_MASK | + GDK_ENTER_NOTIFY_MASK | + GDK_LEAVE_NOTIFY_MASK); GTK_WIDGET_SET_FLAGS(widget, GTK_CAN_FOCUS); g_signal_connect(widget, "size-allocate", @@ -83,6 +86,11 @@ class RenderWidgetHostViewGtkWidget { G_CALLBACK(ButtonPressReleaseEvent), host_view); g_signal_connect(widget, "motion-notify-event", G_CALLBACK(MouseMoveEvent), host_view); + g_signal_connect(widget, "enter-notify-event", + G_CALLBACK(CrossingEvent), host_view); + g_signal_connect(widget, "leave-notify-event", + G_CALLBACK(CrossingEvent), 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", @@ -257,6 +265,14 @@ class RenderWidgetHostViewGtkWidget { return FALSE; } + static gboolean CrossingEvent(GtkWidget* widget, GdkEventCrossing* event, + RenderWidgetHostViewGtk* host_view) { + host_view->GetRenderWidgetHost()->ForwardMouseEvent( + WebInputEventFactory::mouseEvent(event)); + + return FALSE; + } + static gboolean MouseScrollEvent(GtkWidget* widget, GdkEventScroll* event, RenderWidgetHostViewGtk* host_view) { // If the user is holding shift, translate it into a horizontal scroll. We diff --git a/chrome/chrome.gyp b/chrome/chrome.gyp index 470b71aceef1e..907d82b5a9585 100755 --- a/chrome/chrome.gyp +++ b/chrome/chrome.gyp @@ -4205,6 +4205,7 @@ 'test/ui/fast_shutdown_uitest.cc', 'test/ui/history_uitest.cc', 'test/ui/layout_plugin_uitest.cc', + 'test/ui/mouseleave_uitest.cc', 'test/ui/npapi_uitest.cc', 'test/ui/omnibox_uitest.cc', 'test/ui/sandbox_uitests.cc', diff --git a/chrome/test/automation/automation_messages_internal.h b/chrome/test/automation/automation_messages_internal.h index e7cd47faf946c..e8f215c27a7cd 100644 --- a/chrome/test/automation/automation_messages_internal.h +++ b/chrome/test/automation/automation_messages_internal.h @@ -1155,5 +1155,11 @@ IPC_BEGIN_MESSAGES(Automation) int /* Type (Browser::Type) */, bool /* show */ ) + // This message requests that the mouse be moved to this location, in + // window coordinate space. + // Request: + // int - the handle of the window that's the context for this click + // gfx::Point - the location to move to + IPC_MESSAGE_ROUTED2(AutomationMsg_WindowMouseMove, int, gfx::Point) IPC_END_MESSAGES(Automation) diff --git a/chrome/test/automation/window_proxy.cc b/chrome/test/automation/window_proxy.cc index 5b08842850c59..1a8f56bf5bd5d 100644 --- a/chrome/test/automation/window_proxy.cc +++ b/chrome/test/automation/window_proxy.cc @@ -23,6 +23,13 @@ bool WindowProxy::SimulateOSClick(const gfx::Point& click, int flags) { new AutomationMsg_WindowClick(0, handle_, click, flags)); } +bool WindowProxy::SimulateOSMouseMove(const gfx::Point& location) { + if (!is_valid()) return false; + + return sender_->Send( + new AutomationMsg_WindowMouseMove(0, handle_, location)); +} + bool WindowProxy::GetWindowTitle(string16* text) { if (!is_valid()) return false; diff --git a/chrome/test/automation/window_proxy.h b/chrome/test/automation/window_proxy.h index 33a27c028d935..92f74ae1f95cd 100644 --- a/chrome/test/automation/window_proxy.h +++ b/chrome/test/automation/window_proxy.h @@ -44,6 +44,10 @@ class WindowProxy : public AutomationResourceProxy { // window, the top window is clicked. bool SimulateOSClick(const gfx::Point& click, int flags); + // Moves the mouse pointer this location at the OS level. |location| is + // in the window's coordinates. + bool SimulateOSMouseMove(const gfx::Point& location); + // Get the title of the top level window. bool GetWindowTitle(string16* text); diff --git a/chrome/test/data/mouseleave.html b/chrome/test/data/mouseleave.html new file mode 100644 index 0000000000000..dd4f581799a11 --- /dev/null +++ b/chrome/test/data/mouseleave.html @@ -0,0 +1,42 @@ +<html> + <head> + <style> + body { + margin: 0px; + padding: 0px; + } + #mybox { + padding: 20px; + margin: 0px; + border: 1px solid #000; + } + #mystatus { + border: 1px solid #000; + padding: 20px; + margin: 0px; + } + </style> + <script> + var state = ''; + function load() { + state = 'initial'; + document.getElementById("mystatus").innerHTML = state; + document.cookie = '__state=' + state + '; path=/'; + } + function enter() { + state += ',entered'; + document.getElementById("mystatus").innerHTML = state; + document.cookie = '__state=' + state + '; path=/'; + } + function leave() { + state += ',left'; + document.getElementById("mystatus").innerHTML = state; + document.cookie = '__state=' + state + '; path=/'; + } + </script> + </head> + <body onload="load()"> + <div id="mybox" onmouseover="enter()" onmouseout="leave()"></div> + <div id="mystatus"></div> + </body> +</html> diff --git a/chrome/test/ui/mouseleave_uitest.cc b/chrome/test/ui/mouseleave_uitest.cc new file mode 100644 index 0000000000000..8cb59c264f6cd --- /dev/null +++ b/chrome/test/ui/mouseleave_uitest.cc @@ -0,0 +1,99 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/command_line.h" +#include "base/file_util.h" +#include "base/gfx/point.h" +#include "base/gfx/rect.h" +#include "base/path_service.h" +#include "base/string_util.h" +#include "base/values.h" +#include "chrome/browser/view_ids.h" +#include "chrome/common/chrome_paths.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/test/automation/browser_proxy.h" +#include "chrome/test/automation/tab_proxy.h" +#include "chrome/test/automation/window_proxy.h" +#include "chrome/test/ui/javascript_test_util.h" +#include "chrome/test/ui/ui_test.h" +#include "googleurl/src/gurl.h" +#include "net/base/net_util.h" +#include "views/event.h" + +#if defined(OS_MACOSX) +// window->GetViewBounds is not implemented +// window->SimulateOSMouseMove is not implemented +// http://code.google.com/p/chromium/issues/detail?id=26102 +#define MAYBE_TestOnMouseOut DISABLED_TestOnMouseOut +#elif defined(OS_WIN) +// Test succeeds locally, flaky on trybot +// http://code.google.com/p/chromium/issues/detail?id=26349 +#define MAYBE_TestOnMouseOut DISABLED_TestOnMouseOut +#endif + +namespace { + +class MouseLeaveTest : public UITest { + public: + MouseLeaveTest() { + dom_automation_enabled_ = true; + show_window_ = true; + } + + DISALLOW_COPY_AND_ASSIGN(MouseLeaveTest); +}; + +TEST_F(MouseLeaveTest, MAYBE_TestOnMouseOut) { + GURL test_url = GetTestUrl(L"", L"mouseleave.html"); + + scoped_refptr<TabProxy> tab(GetActiveTab()); + ASSERT_TRUE(tab.get()); + scoped_refptr<BrowserProxy> browser = automation()->GetBrowserWindow(0); + scoped_refptr<WindowProxy> window = browser->GetWindow(); + + gfx::Rect tab_view_bounds; + ASSERT_TRUE(window->GetViewBounds(VIEW_ID_TAB_CONTAINER, &tab_view_bounds, + true)); + gfx::Point in_content_point( + tab_view_bounds.x() + tab_view_bounds.width() / 2, + tab_view_bounds.y() + 10); + gfx::Point above_content_point( + tab_view_bounds.x() + tab_view_bounds.width() / 2, + tab_view_bounds.y() - 2); + + // Start by moving the point just above the content. + ASSERT_TRUE(window->SimulateOSMouseMove(above_content_point)); + + // Navigate to the test html page. + tab->NavigateToURL(test_url); + + const int timeout_ms = 5 * action_max_timeout_ms(); + const int check_interval_ms = action_max_timeout_ms() / 10; + + // Wait for the onload() handler to complete so we can do the + // next part of the test. + ASSERT_TRUE(WaitUntilCookieValue( + tab.get(), test_url, "__state", check_interval_ms, timeout_ms, + "initial")); + + // Move the cursor to the top-center of the content, which will trigger + // a javascript onMouseOver event. + ASSERT_TRUE(window->SimulateOSMouseMove(in_content_point)); + + // Wait on the correct intermediate value of the cookie. + ASSERT_TRUE(WaitUntilCookieValue( + tab.get(), test_url, "__state", check_interval_ms, timeout_ms, + "initial,entered")); + + // Move the cursor above the content again, which should trigger + // a javascript onMouseOut event. + ASSERT_TRUE(window->SimulateOSMouseMove(above_content_point)); + + // Wait on the correct final value of the cookie. + ASSERT_TRUE(WaitUntilCookieValue( + tab.get(), test_url, "__state", check_interval_ms, timeout_ms, + "initial,entered,left")); +} + +} // namespace diff --git a/webkit/api/public/gtk/WebInputEventFactory.h b/webkit/api/public/gtk/WebInputEventFactory.h index 5fdfa97fa3d06..f8ce4f0d36db1 100644 --- a/webkit/api/public/gtk/WebInputEventFactory.h +++ b/webkit/api/public/gtk/WebInputEventFactory.h @@ -35,6 +35,7 @@ typedef struct _GdkEventButton GdkEventButton; typedef struct _GdkEventMotion GdkEventMotion; +typedef struct _GdkEventCrossing GdkEventCrossing; typedef struct _GdkEventScroll GdkEventScroll; typedef struct _GdkEventKey GdkEventKey; @@ -50,6 +51,7 @@ namespace WebKit { WEBKIT_API static WebKeyboardEvent keyboardEvent(wchar_t character, int state, double timeStampSeconds); WEBKIT_API static WebMouseEvent mouseEvent(const GdkEventButton*); WEBKIT_API static WebMouseEvent mouseEvent(const GdkEventMotion*); + WEBKIT_API static WebMouseEvent mouseEvent(const GdkEventCrossing*); WEBKIT_API static WebMouseWheelEvent mouseWheelEvent(const GdkEventScroll*); }; diff --git a/webkit/api/src/gtk/WebInputEventFactory.cpp b/webkit/api/src/gtk/WebInputEventFactory.cpp index 6dc9ebc681201..1844e33b432a2 100644 --- a/webkit/api/src/gtk/WebInputEventFactory.cpp +++ b/webkit/api/src/gtk/WebInputEventFactory.cpp @@ -381,6 +381,42 @@ WebMouseEvent WebInputEventFactory::mouseEvent(const GdkEventMotion* event) return result; } +WebMouseEvent WebInputEventFactory::mouseEvent(const GdkEventCrossing* event) +{ + WebMouseEvent result; + + result.timeStampSeconds = gdkEventTimeToWebEventTime(event->time); + result.modifiers = gdkStateToWebEventModifiers(event->state); + result.x = static_cast<int>(event->x); + result.y = static_cast<int>(event->y); + result.windowX = result.x; + result.windowY = result.y; + result.globalX = static_cast<int>(event->x_root); + result.globalY = static_cast<int>(event->y_root); + + switch (event->type) { + case GDK_ENTER_NOTIFY: + case GDK_LEAVE_NOTIFY: + // Note that if we sent MouseEnter or MouseLeave to WebKit, it + // wouldn't work - they don't result in the proper JavaScript events. + // MouseMove does the right thing. + result.type = WebInputEvent::MouseMove; + break; + default: + ASSERT_NOT_REACHED(); + } + + result.button = WebMouseEvent::ButtonNone; + if (event->state & GDK_BUTTON1_MASK) + result.button = WebMouseEvent::ButtonLeft; + else if (event->state & GDK_BUTTON2_MASK) + result.button = WebMouseEvent::ButtonMiddle; + else if (event->state & GDK_BUTTON3_MASK) + result.button = WebMouseEvent::ButtonRight; + + return result; +} + // WebMouseWheelEvent --------------------------------------------------------- WebMouseWheelEvent WebInputEventFactory::mouseWheelEvent(const GdkEventScroll* event) -- GitLab