Commit 9f306d8e authored by Nick Diego Yamane's avatar Nick Diego Yamane Committed by Commit Bot

ozone/wayland: tabdrag: Support dragging upwards to detach a tab

Wayland clients are only aware of surface-local coordinates and there
is no implicit grab during DND sessions. This leads to tab detaching
issues in Chrome's tab drag implementation. Such problems may happen
when the detachable widget occupies any edge area of the window. In this
case specifically, the tab strip is placed at top edge, so dragging a
tab all the way up won't detach it into a new browser window as
expected.

This patch fixes it by dispatching a fake motion event with negative
coordinates everytime a wl_data_device::leave is received in attached
mode, so higher level UI components get a chance to detect when a widget
must be detached, ie: pointer was dragged outside the detachable
widget's bounds.

R=msisov@igalia.com

Bug: 896640
Test: Covered by ozone_unittests
Change-Id: I1a9e5e07763634bb879f4040bf0da24ebfefa79d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2362987
Commit-Queue: Nick Yamane <nickdiego@igalia.com>
Reviewed-by: default avatarMaksim Sisov (GMT+3) <msisov@igalia.com>
Cr-Commit-Position: refs/heads/master@{#799567}
parent fefc6c1f
......@@ -149,15 +149,15 @@ void WaylandDataDevice::OnDrop(void* data, wl_data_device* data_device) {
void WaylandDataDevice::OnLeave(void* data, wl_data_device* data_device) {
auto* self = static_cast<WaylandDataDevice*>(data);
if (self->drag_delegate_) {
if (self->drag_delegate_)
self->drag_delegate_->OnDragLeave();
// When in a DND session initiated by an external application,
// |drag_delegate_| is set at OnEnter, and must be reset here to avoid
// potential use-after-free.
if (!self->drag_delegate_->IsDragSource())
self->drag_delegate_ = nullptr;
}
// When in a DND session initiated by an external application,
// |drag_delegate_| is set at OnEnter, and must be reset here to avoid
// potential use-after-free. Above call to OnDragLeave() may result in
// |drag_delegate_| being reset, so it must be checked here as well.
if (self->drag_delegate_ && !self->drag_delegate_->IsDragSource())
self->drag_delegate_ = nullptr;
}
void WaylandDataDevice::OnSelection(void* data,
......
......@@ -199,9 +199,20 @@ void WaylandWindowDragController::OnDragLeave() {
// which would require hacky workarounds in HandleDropAndResetState function
// to properly detect and handle such cases.
VLOG(1) << "OnLeave";
if (!data_offer_)
return;
VLOG(1) << "OnLeave";
data_offer_.reset();
// As Wayland clients are only aware of surface-local coordinates and there is
// no implicit grab during DND sessions, a fake motion event with negative
// coordinates must be used here to make it possible for higher level UI
// components to detect when a window should be detached. E.g: On Chrome,
// dragging a tab all the way up to the top edge of the window won't work
// without this fake motion event upon wl_data_device::leave events.
if (state_ == State::kAttached)
pointer_delegate_->OnPointerMotionEvent({-1, -1});
}
void WaylandWindowDragController::OnDragDrop() {
......
......@@ -299,6 +299,7 @@ TEST_P(WaylandWindowDragControllerTest, DragExitWindowAndDrop) {
SendPointerEnter(window_.get(), &delegate_);
SendPointerPress(window_.get(), &delegate_, BTN_LEFT);
SendPointerMotion(window_.get(), &delegate_, {10, 10});
Sync();
// Sets up an "interaction flow", start the drag session and run move loop:
// - Event dispatching and bounds changes are monitored
......@@ -311,13 +312,7 @@ TEST_P(WaylandWindowDragControllerTest, DragExitWindowAndDrop) {
auto* move_loop_handler = GetWmMoveLoopHandler(*window_);
DCHECK(move_loop_handler);
enum {
kStarted,
kDragging,
kExitedWindow,
kDropping,
kDone
} test_step = kStarted;
enum { kStarted, kDragging, kExitedDropping, kDone } test_step = kStarted;
EXPECT_CALL(delegate_, DispatchEvent(_)).WillRepeatedly([&](Event* event) {
EXPECT_TRUE(event->IsMouseEvent());
......@@ -332,13 +327,7 @@ TEST_P(WaylandWindowDragControllerTest, DragExitWindowAndDrop) {
SendDndMotion({20, 20});
test_step = kDragging;
break;
case kExitedWindow:
EXPECT_EQ(ET_MOUSE_EXITED, event->type());
// Release mouse button with no window foucsed.
SendDndDrop();
test_step = kDropping;
break;
case kDropping:
case kExitedDropping:
EXPECT_EQ(ET_MOUSE_RELEASED, event->type());
EXPECT_EQ(State::kDropped, drag_controller()->state());
// Ensure PlatformScreen keeps consistent.
......@@ -365,8 +354,9 @@ TEST_P(WaylandWindowDragControllerTest, DragExitWindowAndDrop) {
EXPECT_EQ(kDragging, test_step);
EXPECT_EQ(gfx::Point(20, 20), bounds.origin());
SendDndLeave();
SendDndDrop();
test_step = kDropping;
test_step = kExitedDropping;
});
// RunMoveLoop() blocks until the dragging sessions ends, so resume test
......@@ -536,6 +526,50 @@ TEST_P(WaylandWindowDragControllerTest, DragToOtherWindowSnapDragDrop) {
screen_->GetLocalProcessWidgetAtPoint({20, 20}, {}));
}
// Verifies wl_data_device::leave events are properly handled and propagated
// while in window dragging "attached" mode.
TEST_P(WaylandWindowDragControllerTest, DragExitAttached) {
// Ensure there is no window currently focused
EXPECT_FALSE(window_manager()->GetCurrentFocusedWindow());
EXPECT_EQ(gfx::kNullAcceleratedWidget,
screen_->GetLocalProcessWidgetAtPoint({10, 10}, {}));
SendPointerEnter(window_.get(), &delegate_);
SendPointerPress(window_.get(), &delegate_, BTN_LEFT);
SendPointerMotion(window_.get(), &delegate_, {10, 10});
Sync();
EXPECT_EQ(window_->GetWidget(),
screen_->GetLocalProcessWidgetAtPoint({10, 10}, {}));
auto* wayland_extension = GetWaylandExtension(*window_);
wayland_extension->StartWindowDraggingSessionIfNeeded();
EXPECT_CALL(delegate_, DispatchEvent(_)).Times(1);
Sync();
Sync();
EXPECT_EQ(State::kAttached, drag_controller()->state());
// Emulate a wl_data_device::leave and make sure a motion event is dispatched
// in response.
SendDndLeave();
EXPECT_CALL(delegate_, DispatchEvent(_)).WillOnce([&](Event* event) {
EXPECT_EQ(ET_MOUSE_DRAGGED, event->type());
EXPECT_EQ(gfx::Point(-1, -1).ToString(),
event->AsMouseEvent()->location().ToString());
});
Sync();
SendDndDrop();
EXPECT_CALL(delegate_, DispatchEvent(_)).Times(1);
Sync();
SendPointerEnter(window_.get(), &delegate_);
Sync();
EXPECT_EQ(window_.get(), window_manager()->GetCurrentFocusedWindow());
EXPECT_EQ(window_->GetWidget(),
screen_->GetLocalProcessWidgetAtPoint({20, 20}, {}));
}
INSTANTIATE_TEST_SUITE_P(XdgVersionStableTest,
WaylandWindowDragControllerTest,
::testing::Values(kXdgShellStable));
......
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