Commit a033eb08 authored by Nick Diego Yamane's avatar Nick Diego Yamane Committed by Commit Bot

ozone/wayland: tabdrag: Reliably deliver drop event

Depending on the Wayland compositor, there might be slight deviations in
the order that wl_data_source and wl_pointer events arrive when DND
sessions (with no data exchange) are about to finish, i.e: right after
mouse button is released. Exosphere and recent versions of Gnome Shell,
for example, send a wl_pointer::leave before wl_data_source::cancelled,
which is the only reliable way for the drag controller to determine when
to dispatch the drop event. This event must be sent and to guarantee it
is delivered to the right upper layer UI objects, the correct window
must have pointer focus at ozone/wayland level.

To make the drag controller more resistant to such deviations, this
patch adds a class variable that keeps track of the "pointer grab owner"
during the session and does a little change in the Pointer::Delegate
API so it is possible to target a specific window when dispatching a
mouse button event. Also, a test case is added to exercise such
scenario.

R=tonikitoo@igalia.com

        --gtest_filter='*DragToOtherWindowSnapDragDrop*'

Test: ozone_unittests --ozone-platform=wayland \
Bug: 896640
Change-Id: I67694be2c06f1663ca6f4090c47cb825e9ec150e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2281112
Commit-Queue: Nick Yamane <nickdiego@igalia.com>
Reviewed-by: default avatarAntonio Gomes <tonikitoo@igalia.com>
Cr-Commit-Position: refs/heads/master@{#785198}
parent bcc30a87
......@@ -161,13 +161,18 @@ void WaylandEventSource::OnPointerFocusChanged(WaylandWindow* window,
}
void WaylandEventSource::OnPointerButtonEvent(EventType type,
int changed_button) {
int changed_button,
WaylandWindow* window) {
DCHECK(type == ET_MOUSE_PRESSED || type == ET_MOUSE_RELEASED);
DCHECK(HasAnyPointerButtonFlag(changed_button));
if (!pointer_)
return;
auto* prev_focused_window = window_with_pointer_focus_;
if (window)
HandlePointerFocusChange(window);
pointer_flags_ = type == ET_MOUSE_PRESSED
? (pointer_flags_ | changed_button)
: (pointer_flags_ & ~changed_button);
......@@ -177,6 +182,9 @@ void WaylandEventSource::OnPointerButtonEvent(EventType type,
MouseEvent event(type, pointer_location_, pointer_location_,
EventTimeForNow(), flags, changed_button);
DispatchEvent(&event);
if (window)
HandlePointerFocusChange(prev_focused_window);
}
void WaylandEventSource::OnPointerMotionEvent(const gfx::PointF& location) {
......
......@@ -92,7 +92,9 @@ class WaylandEventSource : public PlatformEventSource,
void OnPointerDestroyed(WaylandPointer* pointer) override;
void OnPointerFocusChanged(WaylandWindow* window,
const gfx::PointF& location) override;
void OnPointerButtonEvent(EventType evtype, int changed_button) override;
void OnPointerButtonEvent(EventType evtype,
int changed_button,
WaylandWindow* window = nullptr) override;
void OnPointerMotionEvent(const gfx::PointF& location) override;
void OnPointerAxisEvent(const gfx::Vector2d& offset) override;
......
......@@ -74,7 +74,9 @@ class WaylandPointer::Delegate {
virtual void OnPointerDestroyed(WaylandPointer* pointer) = 0;
virtual void OnPointerFocusChanged(WaylandWindow* window,
const gfx::PointF& location) = 0;
virtual void OnPointerButtonEvent(EventType evtype, int changed_button) = 0;
virtual void OnPointerButtonEvent(EventType evtype,
int changed_button,
WaylandWindow* window = nullptr) = 0;
virtual void OnPointerMotionEvent(const gfx::PointF& location) = 0;
virtual void OnPointerAxisEvent(const gfx::Vector2d& offset) = 0;
};
......
......@@ -91,9 +91,11 @@ void WaylandWindowDragController::StopDragging() {
VLOG(1) << "End drag loop requested. state=" << state_;
// This function is supposed to be called to indicate that the window was just
// snapped into a tab strip. So switch to |kAttached| state and ask to quit
// the nested loop.
// snapped into a tab strip. So switch to |kAttached| state, store the focused
// window as the pointer grabber and ask to quit the nested loop.
state_ = State::kAttached;
pointer_grab_owner_ = window_manager_->GetCurrentFocusedWindow();
DCHECK(pointer_grab_owner_);
QuitLoop();
}
......@@ -267,6 +269,8 @@ bool WaylandWindowDragController::OfferWindow() {
data_device_->StartDrag(*data_source_, *origin_window_, icon_surface_.get(),
this);
}
pointer_grab_owner_ = origin_window_;
return true;
}
......@@ -298,14 +302,15 @@ void WaylandWindowDragController::HandleMotionEvent(MouseEvent* event) {
// about to finish.
void WaylandWindowDragController::HandleDropAndResetState() {
DCHECK_EQ(state_, State::kDropped);
DCHECK(window_manager_->GetCurrentFocusedWindow());
VLOG(1) << "Notifying drop. window="
<< window_manager_->GetCurrentFocusedWindow();
DCHECK(pointer_grab_owner_);
VLOG(1) << "Notifying drop. window=" << pointer_grab_owner_;
EventFlags pointer_button = EF_LEFT_MOUSE_BUTTON;
DCHECK(connection_->event_source()->IsPointerButtonPressed(pointer_button));
pointer_delegate_->OnPointerButtonEvent(ET_MOUSE_RELEASED, pointer_button);
pointer_delegate_->OnPointerButtonEvent(ET_MOUSE_RELEASED, pointer_button,
pointer_grab_owner_);
pointer_grab_owner_ = nullptr;
state_ = State::kIdle;
}
......
......@@ -118,6 +118,10 @@ class WaylandWindowDragController : public WaylandDataDevice::DragDelegate,
// The current toplevel window being dragged, when in detached mode.
WaylandToplevelWindow* dragged_window_ = nullptr;
// Keeps track of the window that holds the pointer grab. i.e: the owner of
// the surface that must receive the mouse release event upon drop.
WaylandWindow* pointer_grab_owner_ = nullptr;
// The window where the DND session originated from. i.e: which had the
// pointer focus when the session was initiated.
WaylandWindow* origin_window_ = nullptr;
......
......@@ -133,6 +133,17 @@ class WaylandWindowDragControllerTest : public WaylandTest,
EXPECT_EQ(window, window_manager()->GetCurrentFocusedWindow());
}
void SendPointerLeave(WaylandWindow* window,
MockPlatformWindowDelegate* delegate) {
auto* surface = server_.GetObject<wl::MockSurface>(window->GetWidget());
wl_pointer_send_leave(pointer_->resource(), NextSerial(),
surface->resource());
EXPECT_CALL(*delegate, DispatchEvent(_)).Times(1);
Sync();
EXPECT_EQ(nullptr, window_manager()->GetCurrentFocusedWindow());
}
void SendPointerPress(WaylandWindow* window,
MockPlatformWindowDelegate* delegate,
int button) {
......@@ -479,20 +490,22 @@ TEST_P(WaylandWindowDragControllerTest, DragToOtherWindowSnapDragDrop) {
EXPECT_EQ(target_window->GetWidget(),
screen_->GetLocalProcessWidgetAtPoint({50, 50}, {}));
// Emulates a pointer::leave event being sent before data_source::cancelled,
// what happens with some compositors, e.g: Exosphere. Even in these cases,
// WaylandWindowDragController must guarantee the mouse button release event
// (aka: drop) is delivered to the upper layer listeners.
SendPointerLeave(target_window, &delegate_);
SendDndDrop();
EXPECT_CALL(delegate_, DispatchEvent(_)).WillRepeatedly([&](Event* event) {
EXPECT_CALL(delegate_, DispatchEvent(_)).WillOnce([&](Event* event) {
EXPECT_TRUE(event->IsMouseEvent());
switch (test_step) {
case kSnapped:
EXPECT_EQ(ET_MOUSE_RELEASED, event->type());
EXPECT_EQ(State::kDropped, drag_controller()->state());
EXPECT_EQ(target_window, window_manager()->GetCurrentFocusedWindow());
test_step = kDone;
break;
case kDone:
EXPECT_EQ(ET_MOUSE_EXITED, event->type());
EXPECT_EQ(target_window->GetWidget(),
screen_->GetLocalProcessWidgetAtPoint({30, 42}, {}));
break;
default:
FAIL() << " event=" << event->GetName()
<< " state=" << drag_controller()->state()
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment