Index: services/ui/ws/current_drag_operation_unittest.cc |
diff --git a/services/ui/ws/current_drag_operation_unittest.cc b/services/ui/ws/current_drag_operation_unittest.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..8fe48856a57a9a0d936f5256e2c4985ce5f11663 |
--- /dev/null |
+++ b/services/ui/ws/current_drag_operation_unittest.cc |
@@ -0,0 +1,511 @@ |
+// Copyright 2016 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 "services/ui/ws/current_drag_operation.h" |
+#include "services/ui/ws/current_drag_operation_source.h" |
+#include "services/ui/ws/drag_target_connection.h" |
+#include "services/ui/ws/ids.h" |
+#include "services/ui/ws/server_window.h" |
+#include "services/ui/ws/test_server_window_delegate.h" |
+#include "services/ui/ws/test_utils.h" |
+#include "testing/gtest/include/gtest/gtest.h" |
+#include "ui/events/base_event_utils.h" |
+ |
+namespace ui { |
+namespace ws { |
+ |
+enum QueuedDragEventType { |
+ TYPE_NONE, |
+ TYPE_ENTER, |
+ TYPE_OVER, |
+ TYPE_LEAVE, |
+ TYPE_DROP |
+}; |
+ |
+class CurrentDragOperationTest; |
+ |
+// All the classes to represent a window. |
+class DragTestWindow : public DragTargetConnection { |
+ public: |
+ struct DragEvent { |
+ QueuedDragEventType type; |
+ uint32_t key_state; |
+ gfx::Point cursor_offset; |
+ uint32_t effect_bitmask; |
+ base::Callback<void(uint32_t)> callback; |
+ }; |
+ |
+ DragTestWindow(CurrentDragOperationTest* parent, const WindowId& id) |
+ : parent_(parent), window_delegate_(), window_(&window_delegate_, id) { |
+ window_.SetCanAcceptDrags(true); |
+ } |
+ ~DragTestWindow() override; |
+ |
+ TestServerWindowDelegate* delegate() { return &window_delegate_; } |
+ ServerWindow* window() { return &window_; } |
+ |
+ QueuedDragEventType queue_response_type() { |
+ if (queued_callbacks_.empty()) |
+ return TYPE_NONE; |
+ return queued_callbacks_.front().type; |
+ } |
+ |
+ const DragEvent& queue_front() { return queued_callbacks_.front(); } |
+ |
+ size_t queue_size() { return queued_callbacks_.size(); } |
+ |
+ uint32_t times_received_drag_start() { return times_received_drag_start_; } |
+ |
+ // Calls the callback at the front of the queue. |
+ void Respond(bool respond_with_effect) { |
+ if (queued_callbacks_.size()) { |
+ if (!queued_callbacks_.front().callback.is_null()) { |
+ queued_callbacks_.front().callback.Run( |
+ respond_with_effect ? queued_callbacks_.front().effect_bitmask : 0); |
+ } |
+ |
+ queued_callbacks_.pop(); |
+ } |
+ } |
+ |
+ // Overridden from DragTestConnection: |
+ void PerformOnDragStart( |
+ const ServerWindow* window, |
+ mojo::Map<mojo::String, mojo::Array<uint8_t>> mime_data) override { |
+ times_received_drag_start_++; |
+ mime_data_ = std::move(mime_data); |
+ } |
+ |
+ void PerformOnDragEnter( |
+ const ServerWindow* window, |
+ uint32_t key_state, |
+ const gfx::Point& cursor_offset, |
+ uint32_t effect_bitmask, |
+ const base::Callback<void(uint32_t)>& callback) override { |
+ DCHECK_EQ(window, &window_); |
+ queued_callbacks_.push( |
+ {TYPE_ENTER, key_state, cursor_offset, effect_bitmask, callback}); |
+ } |
+ |
+ void PerformOnDragOver( |
+ const ServerWindow* window, |
+ uint32_t key_state, |
+ const gfx::Point& cursor_offset, |
+ uint32_t effect_bitmask, |
+ const base::Callback<void(uint32_t)>& callback) override { |
+ DCHECK_EQ(window, &window_); |
+ queued_callbacks_.push( |
+ {TYPE_OVER, key_state, cursor_offset, effect_bitmask, callback}); |
+ } |
+ |
+ void PerformOnDragLeave(const ServerWindow* window) override { |
+ DCHECK_EQ(window, &window_); |
+ queued_callbacks_.push( |
+ {TYPE_LEAVE, 0, gfx::Point(), 0, base::Callback<void(uint32_t)>()}); |
+ } |
+ |
+ void PerformOnDragDrop( |
+ const ServerWindow* window, |
+ uint32_t key_state, |
+ const gfx::Point& cursor_offset, |
+ uint32_t effect_bitmask, |
+ const base::Callback<void(uint32_t)>& callback) override { |
+ DCHECK_EQ(window, &window_); |
+ queued_callbacks_.push( |
+ {TYPE_DROP, key_state, cursor_offset, effect_bitmask, callback}); |
+ } |
+ |
+ void PerformOnDragFinish(const ServerWindow* window) override { |
+ mime_data_.SetToEmpty(); |
+ } |
+ |
+ private: |
+ CurrentDragOperationTest* parent_; |
+ TestServerWindowDelegate window_delegate_; |
+ ServerWindow window_; |
+ mojo::Map<mojo::String, mojo::Array<uint8_t>> mime_data_; |
+ uint32_t times_received_drag_start_ = 0; |
+ |
+ std::queue<DragEvent> queued_callbacks_; |
+}; |
+ |
+class CurrentDragOperationTest : public testing::Test, |
+ public CurrentDragOperationSource { |
+ public: |
+ std::unique_ptr<DragTestWindow> BuildWindow() { |
+ WindowId id(1, ++window_id_); |
+ std::unique_ptr<DragTestWindow> p = |
+ base::MakeUnique<DragTestWindow>(this, id); |
+ server_window_by_id_[id] = p->window(); |
+ connection_by_window_[p->window()] = p.get(); |
+ return p; |
+ } |
+ |
+ void StartDragOperation( |
+ mojo::Map<mojo::String, mojo::Array<uint8_t>> mime_data, |
+ DragTestWindow* window, |
+ uint32_t drag_operations) { |
+ window->PerformOnDragStart(window->window(), mime_data.Clone()); |
+ drag_operation_.reset(new CurrentDragOperation( |
+ this, window->window(), std::move(mime_data), drag_operations)); |
+ } |
+ |
+ void DispatchDrag(DragTestWindow* window, |
+ bool mouse_released, |
+ uint32_t flags, |
+ const gfx::Point& position) { |
+ ui::PointerEvent event( |
+ ui::MouseEvent(mouse_released ? ET_MOUSE_RELEASED : ET_MOUSE_PRESSED, |
+ position, position, ui::EventTimeForNow(), flags, 0)); |
+ drag_operation_->DispatchLocatedEvent(event, |
+ window ? window->window() : nullptr); |
+ } |
+ |
+ void OnTestWindowDestroyed(DragTestWindow* test_window) { |
+ server_window_by_id_.erase(test_window->window()->id()); |
+ connection_by_window_.erase(test_window->window()); |
+ } |
+ |
+ CurrentDragOperation* drag_operation() const { return drag_operation_.get(); } |
+ const base::Optional<bool>& drag_over_value() { return drag_over_value_; } |
+ |
+ private: |
+ // Overridden from testing::Test: |
+ void SetUp() override { |
+ testing::Test::SetUp(); |
+ |
+ window_delegate_.reset(new TestServerWindowDelegate()); |
+ root_window_.reset( |
+ new ServerWindow(window_delegate_.get(), WindowId(1, 2))); |
+ window_delegate_->set_root_window(root_window_.get()); |
+ root_window_->SetVisible(true); |
+ } |
+ |
+ void TearDown() override { |
+ drag_operation_.reset(); |
+ root_window_.reset(); |
+ window_delegate_.reset(); |
+ |
+ DCHECK(server_window_by_id_.empty()); |
+ DCHECK(connection_by_window_.empty()); |
+ |
+ testing::Test::TearDown(); |
+ } |
+ |
+ // Overridden from CurrentDragOperationSource: |
+ void OnDragOver(bool success) override { drag_over_value_ = success; } |
+ |
+ ServerWindow* GetWindowById(const WindowId& id) override { |
+ auto it = server_window_by_id_.find(id); |
+ if (it == server_window_by_id_.end()) |
+ return nullptr; |
+ return it->second; |
+ } |
+ |
+ DragTargetConnection* GetDragTargetWithRoot( |
+ const ServerWindow* window) override { |
+ auto it = connection_by_window_.find(const_cast<ServerWindow*>(window)); |
+ if (it == connection_by_window_.end()) |
+ return nullptr; |
+ return it->second; |
+ } |
+ |
+ int window_id_ = 3; |
+ |
+ std::map<WindowId, ServerWindow*> server_window_by_id_; |
+ std::map<ServerWindow*, DragTargetConnection*> connection_by_window_; |
+ |
+ std::unique_ptr<TestServerWindowDelegate> window_delegate_; |
+ std::unique_ptr<ServerWindow> root_window_; |
+ |
+ std::unique_ptr<CurrentDragOperation> drag_operation_; |
+ |
+ base::Optional<bool> drag_over_value_; |
+}; |
+ |
+DragTestWindow::~DragTestWindow() { |
+ parent_->OnTestWindowDestroyed(this); |
+} |
+ |
+TEST_F(CurrentDragOperationTest, SimpleDragDrop) { |
+ std::unique_ptr<DragTestWindow> window = BuildWindow(); |
+ mojo::Map<mojo::String, mojo::Array<uint8_t>> mime_data; |
+ StartDragOperation(std::move(mime_data), window.get(), |
+ ui::mojom::kDropEffectMove); |
+ |
+ DispatchDrag(window.get(), false, ui::EF_LEFT_MOUSE_BUTTON, gfx::Point(1, 1)); |
+ EXPECT_EQ(TYPE_ENTER, window->queue_response_type()); |
+ window->Respond(true); |
+ |
+ DispatchDrag(window.get(), false, ui::EF_LEFT_MOUSE_BUTTON, gfx::Point(2, 2)); |
+ EXPECT_EQ(TYPE_OVER, window->queue_response_type()); |
+ window->Respond(true); |
+ |
+ DispatchDrag(window.get(), true, 0, gfx::Point(2, 2)); |
+ EXPECT_EQ(TYPE_DROP, window->queue_response_type()); |
+ window->Respond(true); |
+ |
+ EXPECT_TRUE(drag_over_value().value_or(false)); |
+} |
+ |
+TEST_F(CurrentDragOperationTest, OnlyDeliverMimeDataOnce) { |
+ std::unique_ptr<DragTestWindow> window1 = BuildWindow(); |
+ std::unique_ptr<DragTestWindow> window2 = BuildWindow(); |
+ mojo::Map<mojo::String, mojo::Array<uint8_t>> mime_data; |
+ |
+ // The client lib is responsible for sending the data to the window that's |
+ // the drag source to minimize IPC. |
+ EXPECT_EQ(0u, window1->times_received_drag_start()); |
+ StartDragOperation(std::move(mime_data), window1.get(), |
+ ui::mojom::kDropEffectMove); |
+ EXPECT_EQ(1u, window1->times_received_drag_start()); |
+ DispatchDrag(window1.get(), false, ui::EF_LEFT_MOUSE_BUTTON, |
+ gfx::Point(1, 1)); |
+ EXPECT_EQ(1u, window1->times_received_drag_start()); |
+ window1->Respond(true); |
+ |
+ // Window2 doesn't receive the drag data until mouse is over it. |
+ EXPECT_EQ(0u, window2->times_received_drag_start()); |
+ DispatchDrag(window2.get(), false, ui::EF_LEFT_MOUSE_BUTTON, |
+ gfx::Point(2, 2)); |
+ EXPECT_EQ(1u, window2->times_received_drag_start()); |
+ |
+ // Moving back to the source window doesn't send an additional start message. |
+ DispatchDrag(window1.get(), false, ui::EF_LEFT_MOUSE_BUTTON, |
+ gfx::Point(1, 1)); |
+ EXPECT_EQ(1u, window1->times_received_drag_start()); |
+ |
+ // Moving back to window2 doesn't send an additional start message. |
+ DispatchDrag(window2.get(), false, ui::EF_LEFT_MOUSE_BUTTON, |
+ gfx::Point(1, 1)); |
+ EXPECT_EQ(1u, window2->times_received_drag_start()); |
+} |
+ |
+TEST_F(CurrentDragOperationTest, FailWhenDropOverNoWindow) { |
+ std::unique_ptr<DragTestWindow> window = BuildWindow(); |
+ mojo::Map<mojo::String, mojo::Array<uint8_t>> mime_data; |
+ StartDragOperation(std::move(mime_data), window.get(), |
+ ui::mojom::kDropEffectMove); |
+ |
+ DispatchDrag(window.get(), false, ui::EF_LEFT_MOUSE_BUTTON, gfx::Point(1, 1)); |
+ EXPECT_EQ(TYPE_ENTER, window->queue_response_type()); |
+ window->Respond(true); |
+ |
+ DispatchDrag(window.get(), false, ui::EF_LEFT_MOUSE_BUTTON, gfx::Point(2, 2)); |
+ EXPECT_EQ(TYPE_OVER, window->queue_response_type()); |
+ window->Respond(true); |
+ |
+ DispatchDrag(nullptr, true, 0, gfx::Point(2, 2)); |
+ // Moving outside of |window| should result in |window| getting a leave. |
+ EXPECT_EQ(TYPE_LEAVE, window->queue_response_type()); |
+ window->Respond(true); |
+ |
+ EXPECT_FALSE(drag_over_value().value_or(true)); |
+} |
+ |
+TEST_F(CurrentDragOperationTest, EnterLeaveWhenMovingBetweenTwoWindows) { |
+ std::unique_ptr<DragTestWindow> window1 = BuildWindow(); |
+ std::unique_ptr<DragTestWindow> window2 = BuildWindow(); |
+ mojo::Map<mojo::String, mojo::Array<uint8_t>> mime_data; |
+ StartDragOperation(std::move(mime_data), window1.get(), |
+ ui::mojom::kDropEffectMove); |
+ |
+ DispatchDrag(window1.get(), false, ui::EF_LEFT_MOUSE_BUTTON, |
+ gfx::Point(1, 1)); |
+ EXPECT_EQ(TYPE_ENTER, window1->queue_response_type()); |
+ window1->Respond(true); |
+ |
+ DispatchDrag(window2.get(), false, ui::EF_LEFT_MOUSE_BUTTON, |
+ gfx::Point(2, 2)); |
+ EXPECT_EQ(TYPE_ENTER, window2->queue_response_type()); |
+ EXPECT_EQ(TYPE_LEAVE, window1->queue_response_type()); |
+ window1->Respond(true); |
+ window2->Respond(true); |
+} |
+ |
+TEST_F(CurrentDragOperationTest, EnterToOverQueued) { |
+ std::unique_ptr<DragTestWindow> window = BuildWindow(); |
+ mojo::Map<mojo::String, mojo::Array<uint8_t>> mime_data; |
+ StartDragOperation(std::move(mime_data), window.get(), |
+ ui::mojom::kDropEffectMove); |
+ |
+ DispatchDrag(window.get(), false, ui::EF_LEFT_MOUSE_BUTTON, gfx::Point(1, 1)); |
+ ASSERT_EQ(1u, window->queue_size()); |
+ EXPECT_EQ(TYPE_ENTER, window->queue_response_type()); |
+ // Don't respond. |
+ |
+ // We don't receive another message since we haven't acknowledged the first. |
+ DispatchDrag(window.get(), false, ui::EF_LEFT_MOUSE_BUTTON, gfx::Point(1, 2)); |
+ ASSERT_EQ(1u, window->queue_size()); |
+ |
+ // Responding causes us to receive our next event. |
+ window->Respond(true); |
+ ASSERT_EQ(1u, window->queue_size()); |
+ EXPECT_EQ(TYPE_OVER, window->queue_response_type()); |
+} |
+ |
+TEST_F(CurrentDragOperationTest, CoalesceMouseOverEvents) { |
+ std::unique_ptr<DragTestWindow> window = BuildWindow(); |
+ mojo::Map<mojo::String, mojo::Array<uint8_t>> mime_data; |
+ StartDragOperation(std::move(mime_data), window.get(), |
+ ui::mojom::kDropEffectMove); |
+ |
+ DispatchDrag(window.get(), false, ui::EF_LEFT_MOUSE_BUTTON, gfx::Point(1, 1)); |
+ EXPECT_EQ(TYPE_ENTER, window->queue_response_type()); |
+ |
+ DispatchDrag(window.get(), false, ui::EF_LEFT_MOUSE_BUTTON, gfx::Point(1, 2)); |
+ DispatchDrag(window.get(), false, ui::EF_LEFT_MOUSE_BUTTON, gfx::Point(2, 2)); |
+ DispatchDrag(window.get(), false, ui::EF_LEFT_MOUSE_BUTTON, gfx::Point(2, 3)); |
+ |
+ // Responding to the first delivers us the last mouse over event's position. |
+ window->Respond(true); |
+ ASSERT_EQ(1u, window->queue_size()); |
+ EXPECT_EQ(TYPE_OVER, window->queue_response_type()); |
+ EXPECT_EQ(gfx::Point(2, 3), window->queue_front().cursor_offset); |
+ |
+ // There are no queued events because they were coalesced. |
+ window->Respond(true); |
+ EXPECT_EQ(0u, window->queue_size()); |
+} |
+ |
+TEST_F(CurrentDragOperationTest, RemovePendingMouseOversOnLeave) { |
+ std::unique_ptr<DragTestWindow> window1 = BuildWindow(); |
+ std::unique_ptr<DragTestWindow> window2 = BuildWindow(); |
+ mojo::Map<mojo::String, mojo::Array<uint8_t>> mime_data; |
+ StartDragOperation(std::move(mime_data), window1.get(), |
+ ui::mojom::kDropEffectMove); |
+ |
+ // Enter |
+ DispatchDrag(window1.get(), false, ui::EF_LEFT_MOUSE_BUTTON, |
+ gfx::Point(1, 1)); |
+ EXPECT_EQ(TYPE_ENTER, window1->queue_response_type()); |
+ |
+ // Over |
+ DispatchDrag(window1.get(), false, ui::EF_LEFT_MOUSE_BUTTON, |
+ gfx::Point(1, 1)); |
+ |
+ // Leave |
+ DispatchDrag(window2.get(), false, ui::EF_LEFT_MOUSE_BUTTON, |
+ gfx::Point(1, 1)); |
+ |
+ // The window finally responds to the enter message; we should not receive |
+ // any over messages since we didn't respond to the enter message in time. |
+ window1->Respond(true); |
+ ASSERT_EQ(1u, window1->queue_size()); |
+ EXPECT_EQ(TYPE_LEAVE, window1->queue_response_type()); |
+} |
+ |
+TEST_F(CurrentDragOperationTest, TargetWindowClosedWhileDrag) { |
+ std::unique_ptr<DragTestWindow> window1 = BuildWindow(); |
+ std::unique_ptr<DragTestWindow> window2 = BuildWindow(); |
+ mojo::Map<mojo::String, mojo::Array<uint8_t>> mime_data; |
+ StartDragOperation(std::move(mime_data), window1.get(), |
+ ui::mojom::kDropEffectMove); |
+ |
+ test::CurrentDragOperationTestApi api(drag_operation()); |
+ |
+ // Send some events to |window|. |
+ DispatchDrag(window2.get(), false, ui::EF_LEFT_MOUSE_BUTTON, |
+ gfx::Point(1, 1)); |
+ EXPECT_EQ(TYPE_ENTER, window2->queue_response_type()); |
+ DispatchDrag(window2.get(), false, ui::EF_LEFT_MOUSE_BUTTON, |
+ gfx::Point(1, 1)); |
+ |
+ ServerWindow* server_window = window2->window(); |
+ |
+ // Ensure that CurrentDragOperation is waiting for a response from |window|. |
+ EXPECT_EQ(2u, api.GetSizeOfQueueForWindow(server_window)); |
+ EXPECT_EQ(server_window, api.GetCurrentTarget()); |
+ |
+ // Force the destruction of |window.window|. |
+ window2.reset(); |
+ |
+ // CurrentDragOperation doesn't know anything about the server window now. |
+ EXPECT_EQ(0u, api.GetSizeOfQueueForWindow(server_window)); |
+ EXPECT_EQ(nullptr, api.GetCurrentTarget()); |
+ |
+ // But a target window closing out from under us doesn't fail the drag. |
+ EXPECT_FALSE(drag_over_value().has_value()); |
+} |
+ |
+TEST_F(CurrentDragOperationTest, SourceWindowClosedWhileDrag) { |
+ std::unique_ptr<DragTestWindow> window1 = BuildWindow(); |
+ std::unique_ptr<DragTestWindow> window2 = BuildWindow(); |
+ mojo::Map<mojo::String, mojo::Array<uint8_t>> mime_data; |
+ StartDragOperation(std::move(mime_data), window1.get(), |
+ ui::mojom::kDropEffectMove); |
+ |
+ test::CurrentDragOperationTestApi api(drag_operation()); |
+ |
+ // Send some events to |window|. |
+ DispatchDrag(window2.get(), false, ui::EF_LEFT_MOUSE_BUTTON, |
+ gfx::Point(1, 1)); |
+ EXPECT_EQ(TYPE_ENTER, window2->queue_response_type()); |
+ DispatchDrag(window2.get(), false, ui::EF_LEFT_MOUSE_BUTTON, |
+ gfx::Point(1, 1)); |
+ |
+ ServerWindow* server_window = window2->window(); |
+ |
+ // Ensure that CurrentDragOperation is waiting for a response from |window|. |
+ EXPECT_EQ(2u, api.GetSizeOfQueueForWindow(server_window)); |
+ EXPECT_EQ(server_window, api.GetCurrentTarget()); |
+ |
+ // Force the destruction of the source window. |
+ window1.reset(); |
+ |
+ // The source window going away fails the drag. |
+ EXPECT_FALSE(drag_over_value().value_or(true)); |
+} |
+ |
+TEST_F(CurrentDragOperationTest, DontQueueEventsAfterDrop) { |
+ // The CurrentDragOperation needs to stick around to coordinate the drop, but |
+ // it should ignore further mouse events during this time. |
+ std::unique_ptr<DragTestWindow> window = BuildWindow(); |
+ mojo::Map<mojo::String, mojo::Array<uint8_t>> mime_data; |
+ StartDragOperation(std::move(mime_data), window.get(), |
+ ui::mojom::kDropEffectMove); |
+ |
+ test::CurrentDragOperationTestApi api(drag_operation()); |
+ |
+ DispatchDrag(window.get(), false, ui::EF_LEFT_MOUSE_BUTTON, gfx::Point(1, 1)); |
+ EXPECT_EQ(TYPE_ENTER, window->queue_response_type()); |
+ window->Respond(true); |
+ |
+ DispatchDrag(window.get(), false, ui::EF_LEFT_MOUSE_BUTTON, gfx::Point(2, 2)); |
+ EXPECT_EQ(TYPE_OVER, window->queue_response_type()); |
+ window->Respond(true); |
+ |
+ DispatchDrag(window.get(), true, 0, gfx::Point(2, 2)); |
+ EXPECT_EQ(TYPE_DROP, window->queue_response_type()); |
+ EXPECT_EQ(1u, api.GetSizeOfQueueForWindow(window->window())); |
+ |
+ // Further located events don't result in additional drag messages. |
+ DispatchDrag(window.get(), false, ui::EF_LEFT_MOUSE_BUTTON, gfx::Point(2, 2)); |
+ DispatchDrag(window.get(), false, ui::EF_LEFT_MOUSE_BUTTON, gfx::Point(2, 2)); |
+ EXPECT_EQ(1u, api.GetSizeOfQueueForWindow(window->window())); |
+} |
+ |
+TEST_F(CurrentDragOperationTest, CancelDrag) { |
+ // The CurrentDragOperation needs to stick around to coordinate the drop, but |
+ // it should ignore further mouse events during this time. |
+ std::unique_ptr<DragTestWindow> window = BuildWindow(); |
+ mojo::Map<mojo::String, mojo::Array<uint8_t>> mime_data; |
+ StartDragOperation(std::move(mime_data), window.get(), |
+ ui::mojom::kDropEffectMove); |
+ |
+ DispatchDrag(window.get(), false, ui::EF_LEFT_MOUSE_BUTTON, gfx::Point(1, 1)); |
+ EXPECT_EQ(TYPE_ENTER, window->queue_response_type()); |
+ window->Respond(true); |
+ |
+ drag_operation()->DispatchCancel(); |
+ |
+ EXPECT_FALSE(drag_over_value().value_or(true)); |
+} |
+ |
+// TODO(erg): Add a test to ensure windows that the cursor isn't over |
+// responding to messages don't change the cursor when we have cursor handling |
+// code. |
+ |
+} // namespace ws |
+} // namespace ui |