diff --git a/chrome/browser/automation/automation_provider.cc b/chrome/browser/automation/automation_provider.cc
index 63d2a452e8b9ffc4aac93b01346866a1d21a6ce6..64b013382b2ee309995923bcb2eaede600d97549 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 65b7795554406769c84007398636b2d1347d2130..dc540da868574ec6af957310e9992064cb5b82c6 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 250e0ab3ba204275cf2283da34de34592726d24b..e89aef2cbe47debe0333e769c382306da71f6c6a 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 470b71aceef1e36a4c7c42ba6a33b4aedd5d0139..907d82b5a95859e71581af17ed01e849186f245b 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 e7cd47faf946c842401f8019729cebe316835295..e8f215c27a7cdfb4d62396bd7870e332c8a1603c 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 5b08842850c59b4d1a28224e609b23fb4ba07e24..1a8f56bf5bd5d48b811a95ffdc0d8d7bf78afdac 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 33a27c028d9352393e4c3432517aa2fea36302c8..92f74ae1f95cd137b31afa9bbab85992dc5afad5 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 0000000000000000000000000000000000000000..dd4f581799a11aa1b551b379f08ee3d922572493
--- /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 0000000000000000000000000000000000000000..8cb59c264f6cd7b5a05d320297c34ee64aadc1ff
--- /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 5fdfa97fa3d066c72868ca9dd871c502bab6b59b..f8ce4f0d36db10d5ceae7ecea042c6e689d641b0 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 6dc9ebc681201077a4e7ff9430ed613ce2151ef3..1844e33b432a2b3b7e6e2f4338e80d601a82f6ec 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)