diff --git a/chrome/browser/automation/automation_provider_gtk.cc b/chrome/browser/automation/automation_provider_gtk.cc
index 7036bf39a3dae80b611e72678db27b3695a8c823..23e85b941870cafd73d9a77ef0bb251f48ddef08 100644
--- a/chrome/browser/automation/automation_provider_gtk.cc
+++ b/chrome/browser/automation/automation_provider_gtk.cc
@@ -4,8 +4,11 @@
 
 #include "chrome/browser/automation/automation_provider.h"
 
+#include <gtk/gtk.h>
+
 #include "base/gfx/point.h"
 #include "base/gfx/rect.h"
+#include "chrome/browser/automation/ui_controls.h"
 #include "chrome/browser/gtk/browser_window_gtk.h"
 #include "chrome/browser/gtk/view_id_util.h"
 #include "chrome/common/gtk_util.h"
@@ -94,14 +97,123 @@ void AutomationProvider::GetBookmarkBarVisibility(int handle, bool* visible,
   NOTIMPLEMENTED();
 }
 
+// This task sends a WindowDragResponse message with the appropriate
+// routing ID to the automation proxy.  This is implemented as a task so that
+// we know that the mouse events (and any tasks that they spawn on the message
+// loop) have been processed by the time this is sent.
+class WindowDragResponseTask : public Task {
+ public:
+  WindowDragResponseTask(AutomationProvider* provider,
+                         IPC::Message* reply_message)
+      : provider_(provider),
+        reply_message_(reply_message) {
+    DCHECK(provider_);
+    DCHECK(reply_message_);
+  }
+
+  virtual ~WindowDragResponseTask() {
+  }
+
+  virtual void Run() {
+    AutomationMsg_WindowDrag::WriteReplyParams(reply_message_, true);
+    provider_->Send(reply_message_);
+  }
+
+ private:
+  AutomationProvider* provider_;
+  IPC::Message* reply_message_;
+
+  DISALLOW_COPY_AND_ASSIGN(WindowDragResponseTask);
+};
+
+// A task that just runs a SendMouseEvent and performs another task when done.
+class MouseEventTask : public Task {
+ public:
+  MouseEventTask(Task* next_task, ui_controls::MouseButtonState state)
+      : next_task_(next_task),
+        state_(state) {}
+
+  virtual ~MouseEventTask() {
+  }
+
+  virtual void Run() {
+    ui_controls::SendMouseEventsNotifyWhenDone(ui_controls::LEFT, state_,
+                                               next_task_);
+  }
+
+ private:
+  // The task to execute when we are done.
+  Task* next_task_;
+
+  // Mouse press or mouse release.
+  ui_controls::MouseButtonState state_;
+
+  DISALLOW_COPY_AND_ASSIGN(MouseEventTask);
+};
+
+// A task that just runs a SendMouseMove and performs another task when done.
+class MouseMoveTask : public Task {
+ public:
+  MouseMoveTask(Task* next_task, int absolute_x, int absolute_y)
+      : next_task_(next_task),
+        x_(absolute_x),
+        y_(absolute_y) {
+  }
+
+  virtual ~MouseMoveTask() {
+  }
+
+  virtual void Run() {
+    ui_controls::SendMouseMoveNotifyWhenDone(x_, y_, next_task_);
+  }
+
+ private:
+  // The task to execute when we are done.
+  Task* next_task_;
+
+  // Coordinates of the press.
+  int x_;
+  int y_;
+
+  DISALLOW_COPY_AND_ASSIGN(MouseMoveTask);
+};
+
 void AutomationProvider::WindowSimulateDrag(int handle,
                                             std::vector<gfx::Point> drag_path,
                                             int flags,
                                             bool press_escape_en_route,
                                             IPC::Message* reply_message) {
-  NOTIMPLEMENTED();
-  AutomationMsg_WindowDrag::WriteReplyParams(reply_message, false);
-  Send(reply_message);
+  // TODO(estade): don't ignore |flags| or |escape_en_route|.
+  gfx::NativeWindow window =
+      browser_tracker_->GetResource(handle)->window()->GetNativeHandle();
+  if (window && (drag_path.size() > 1)) {
+    int x, y;
+    gdk_window_get_position(GTK_WIDGET(window)->window, &x, &y);
+
+    // Create a nested stack of tasks to run.
+    Task* next_task = new WindowDragResponseTask(this, reply_message);
+    next_task = new MouseEventTask(next_task, ui_controls::UP);
+    next_task = new MouseEventTask(next_task, ui_controls::UP);
+    for (size_t i = drag_path.size() - 1; i > 0; --i) {
+      // Smooth out the mouse movements by adding intermediate points. This
+      // better simulates a real user drag.
+      int dest_x = drag_path[i].x() + x;
+      int dest_y = drag_path[i].y() + y;
+      int half_step_x = (dest_x + drag_path[i - 1].x() + x) / 2;
+      int half_step_y = (dest_y + drag_path[i - 1].y() + y) / 2;
+
+      next_task = new MouseMoveTask(next_task, dest_x, dest_y);
+      next_task = new MouseMoveTask(next_task, half_step_x, half_step_y);
+    }
+    next_task = new MouseEventTask(next_task, ui_controls::DOWN);
+
+    ui_controls::SendMouseMoveNotifyWhenDone(x + drag_path[0].x(),
+                                             y + drag_path[0].y(),
+                                             next_task);
+  } else {
+    AutomationMsg_WindowDrag::WriteReplyParams(reply_message, false);
+    Send(reply_message);
+  }
 }
 
 void AutomationProvider::TerminateSession(int handle, bool* success) {
diff --git a/chrome/browser/automation/ui_controls_linux.cc b/chrome/browser/automation/ui_controls_linux.cc
index aa18ce21869ef29e0dc1bafba2ba25990e083364..6b2deeec7942ce6092ab10d8b596150dc2a378f1 100644
--- a/chrome/browser/automation/ui_controls_linux.cc
+++ b/chrome/browser/automation/ui_controls_linux.cc
@@ -58,9 +58,9 @@ class EventWaiter : public MessageLoopForUI::Observer {
   }
 
  private:
-  // We pass ownership of task_ to MessageLoop when the corrent event is
+  // We pass ownership of task_ to MessageLoop when the current event is
   // received.
-  Task *task_;
+  Task* task_;
   GdkEventType type_;
   // The number of events of this type to wait for.
   int count_;
@@ -87,7 +87,6 @@ class ClickTask : public Task {
   Task* followup_;
 };
 
-
 bool SendKeyEvent(GdkWindow* window, bool press, guint key, guint state) {
   GdkEvent* event = gdk_event_new(press ? GDK_KEY_PRESS : GDK_KEY_RELEASE);
 
@@ -116,6 +115,35 @@ bool SendKeyEvent(GdkWindow* window, bool press, guint key, guint state) {
   return true;
 }
 
+void FakeAMouseMotionEvent(gint x, gint y) {
+  GdkEvent* event = gdk_event_new(GDK_MOTION_NOTIFY);
+
+  event->motion.send_event = false;
+  event->motion.time = EventTimeNow();
+
+  GtkWidget* grab_widget = gtk_grab_get_current();
+  if (grab_widget) {
+    // If there is a grab, we need to target all events at it regardless of
+    // what widget the mouse is over.
+    event->motion.window = grab_widget->window;
+  } else {
+    event->motion.window = gdk_window_at_pointer(&x, &y);
+  }
+  g_object_ref(event->motion.window);
+  event->motion.x = x;
+  event->motion.y = y;
+  gint origin_x, origin_y;
+  gdk_window_get_origin(event->motion.window, &origin_x, &origin_y);
+  event->motion.x_root = x + origin_x;
+  event->motion.y_root = y + origin_y;
+
+  event->motion.device = gdk_device_get_core_pointer();
+  event->type = GDK_MOTION_NOTIFY;
+
+  gdk_event_put(event);
+  gdk_event_free(event);
+}
+
 }  // namespace
 
 namespace ui_controls {
@@ -205,13 +233,18 @@ bool SendKeyPressNotifyWhenDone(gfx::NativeWindow window, wchar_t key,
 bool SendMouseMove(long x, long y) {
   gdk_display_warp_pointer(gdk_display_get_default(), gdk_screen_get_default(),
                            x, y);
+  // Sometimes gdk_display_warp_pointer fails to send back any indication of
+  // the move, even though it succesfully moves the server cursor. We fake it in
+  // order to get drags to work.
+  FakeAMouseMotionEvent(x, y);
+
   return true;
 }
 
 bool SendMouseMoveNotifyWhenDone(long x, long y, Task* task) {
   bool rv = SendMouseMove(x, y);
   // We can't rely on any particular event signalling the completion of the
-  // mouse move. Posting the task to the message loop should gaurantee
+  // mouse move. Posting the task to the message loop hopefully guarantees
   // the pointer has moved before task is run (although it may not run it as
   // soon as it could).
   MessageLoop::current()->PostTask(FROM_HERE, task);
@@ -234,17 +267,18 @@ bool SendMouseEvents(MouseButton type, int state) {
   } else {
     event->button.window = gdk_window_at_pointer(&x, &y);
   }
+
   g_object_ref(event->button.window);
-  event->motion.x = x;
-  event->motion.y = y;
+  event->button.x = x;
+  event->button.y = y;
   gint origin_x, origin_y;
   gdk_window_get_origin(event->button.window, &origin_x, &origin_y);
   event->button.x_root = x + origin_x;
   event->button.y_root = y + origin_y;
 
   event->button.axes = NULL;
-  // TODO(estade): as above, we may want to pack this with the actual state.
-  event->button.state = 0;
+  gdk_window_get_pointer(event->button.window, NULL, NULL,
+      reinterpret_cast<GdkModifierType*>(&event->button.state));
   event->button.button = type == LEFT ? 1 : (type == MIDDLE ? 2 : 3);
   event->button.device = gdk_device_get_core_pointer();
 
diff --git a/chrome/browser/gtk/tabs/tab_gtk.cc b/chrome/browser/gtk/tabs/tab_gtk.cc
index 3850da99131457462499e07ced30f47f56363187..904edc3bc6d03fe8cfce48fa6ce81fb08d47dbfc 100644
--- a/chrome/browser/gtk/tabs/tab_gtk.cc
+++ b/chrome/browser/gtk/tabs/tab_gtk.cc
@@ -218,24 +218,27 @@ void TabGtk::WillProcessEvent(GdkEvent* event) {
 }
 
 void TabGtk::DidProcessEvent(GdkEvent* event) {
-  if (event->type != GDK_MOTION_NOTIFY)
+  if (!(event->type == GDK_MOTION_NOTIFY || event->type == GDK_LEAVE_NOTIFY ||
+        event->type == GDK_ENTER_NOTIFY)) {
     return;
+  }
 
   if (drag_widget_) {
     delegate_->ContinueDrag(NULL);
     return;
   }
 
-  GdkEventMotion* motion = reinterpret_cast<GdkEventMotion*>(event);
-  GdkEventButton* button = reinterpret_cast<GdkEventButton*>(last_mouse_down_);
-  bool dragging = gtk_drag_check_threshold(widget(),
-                                           static_cast<gint>(button->x),
-                                           static_cast<gint>(button->y),
-                                           static_cast<gint>(motion->x),
-                                           static_cast<gint>(motion->y));
-  if (dragging) {
-    StartDragging(gfx::Point(static_cast<int>(button->x),
-                             static_cast<int>(button->y)));
+  gint old_x = static_cast<gint>(last_mouse_down_->button.x_root);
+  gint old_y = static_cast<gint>(last_mouse_down_->button.y_root);
+  gdouble new_x;
+  gdouble new_y;
+  gdk_event_get_root_coords(event, &new_x, &new_y);
+
+  if (gtk_drag_check_threshold(widget(), old_x, old_y,
+      static_cast<gint>(new_x), static_cast<gint>(new_y))) {
+    StartDragging(gfx::Point(
+        static_cast<int>(last_mouse_down_->button.x),
+        static_cast<int>(last_mouse_down_->button.y)));
   }
 }
 
diff --git a/chrome/test/automated_ui_tests/automated_ui_test_base.cc b/chrome/test/automated_ui_tests/automated_ui_test_base.cc
index 81ed10eb55eebfe6b4e71b333650d1058c93cd8a..9880fd38f7270cdf215fa55b794ef253e4110618 100644
--- a/chrome/test/automated_ui_tests/automated_ui_test_base.cc
+++ b/chrome/test/automated_ui_tests/automated_ui_test_base.cc
@@ -17,12 +17,14 @@ AutomatedUITestBase::AutomatedUITestBase() {}
 
 AutomatedUITestBase::~AutomatedUITestBase() {}
 
-void AutomatedUITestBase::LogErrorMessage(const std::string& error) {}
+void AutomatedUITestBase::LogErrorMessage(const std::string& error) {
+}
 
 void AutomatedUITestBase::LogWarningMessage(const std::string& warning) {
 }
 
-void AutomatedUITestBase::LogInfoMessage(const std::string& info) {}
+void AutomatedUITestBase::LogInfoMessage(const std::string& info) {
+}
 
 void AutomatedUITestBase::SetUp() {
   UITest::SetUp();
diff --git a/chrome/test/automated_ui_tests/automated_ui_test_interactive_test.cc b/chrome/test/automated_ui_tests/automated_ui_test_interactive_test.cc
index f7ac5fc44a53ddec033a5e5001b52fe754015b30..e1d2e64d21ec5a7187004c82f9ff961e5ff9e265 100644
--- a/chrome/test/automated_ui_tests/automated_ui_test_interactive_test.cc
+++ b/chrome/test/automated_ui_tests/automated_ui_test_interactive_test.cc
@@ -7,12 +7,6 @@
 #include "chrome/test/automation/tab_proxy.h"
 #include "chrome/test/ui/ui_test.h"
 
-#if defined(OS_WINDOWS)
-#define MAYBE(x) x
-#else
-#define MAYBE(x) DISABLED_##x
-#endif
-
 namespace {
 
 bool WaitForURLDisplayedForTab(BrowserProxy* browser, int tab_index,
@@ -32,7 +26,7 @@ bool WaitForURLDisplayedForTab(BrowserProxy* browser, int tab_index,
 
 }  // namespace
 
-TEST_F(AutomatedUITestBase, MAYBE(DragOut)) {
+TEST_F(AutomatedUITestBase, DragOut) {
   int tab_count;
   active_browser()->GetTabCount(&tab_count);
   ASSERT_EQ(1, tab_count);
@@ -54,7 +48,7 @@ TEST_F(AutomatedUITestBase, MAYBE(DragOut)) {
   ASSERT_EQ(2, window_count);
 }
 
-TEST_F(AutomatedUITestBase, MAYBE(DragLeftRight)) {
+TEST_F(AutomatedUITestBase, DragLeftRight) {
   int tab_count;
   active_browser()->GetTabCount(&tab_count);
   ASSERT_EQ(1, tab_count);