123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794 |
- // Copyright 2020 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 "components/exo/extended_drag_source.h"
- #include <memory>
- #include <string>
- #include "ash/constants/app_types.h"
- #include "ash/drag_drop/drag_drop_controller.h"
- #include "ash/drag_drop/toplevel_window_drag_delegate.h"
- #include "ash/public/cpp/tablet_mode.h"
- #include "ash/public/cpp/window_properties.h"
- #include "ash/shell.h"
- #include "ash/wm/toplevel_window_event_handler.h"
- #include "ash/wm/window_state.h"
- #include "base/strings/utf_string_conversions.h"
- #include "base/test/bind.h"
- #include "base/test/gmock_callback_support.h"
- #include "chromeos/ui/base/window_properties.h"
- #include "components/exo/buffer.h"
- #include "components/exo/data_source.h"
- #include "components/exo/data_source_delegate.h"
- #include "components/exo/drag_drop_operation.h"
- #include "components/exo/seat.h"
- #include "components/exo/shell_surface.h"
- #include "components/exo/surface.h"
- #include "components/exo/test/exo_test_base.h"
- #include "components/exo/test/exo_test_data_exchange_delegate.h"
- #include "components/exo/test/exo_test_helper.h"
- #include "components/exo/test/shell_surface_builder.h"
- #include "testing/gmock/include/gmock/gmock.h"
- #include "testing/gtest/include/gtest/gtest.h"
- #include "ui/aura/client/aura_constants.h"
- #include "ui/aura/client/drag_drop_client.h"
- #include "ui/aura/client/drag_drop_delegate.h"
- #include "ui/aura/window_tree_host.h"
- #include "ui/base/dragdrop/drag_drop_types.h"
- #include "ui/base/dragdrop/mojom/drag_drop_types.mojom-shared.h"
- #include "ui/base/dragdrop/os_exchange_data.h"
- #include "ui/events/event_utils.h"
- #include "ui/events/test/event_generator.h"
- #include "ui/events/test/events_test_utils.h"
- #include "ui/gfx/geometry/point.h"
- #include "ui/gfx/geometry/size.h"
- #include "ui/gfx/geometry/vector2d.h"
- using ::testing::_;
- using ::testing::AnyNumber;
- using ::testing::DoAll;
- using ::testing::InvokeWithoutArgs;
- using ::testing::SaveArg;
- namespace exo {
- namespace {
- void DispatchGesture(ui::EventType gesture_type, gfx::Point location) {
- ui::GestureEventDetails event_details(gesture_type);
- ui::GestureEvent gesture_event(location.x(), location.y(), 0,
- ui::EventTimeForNow(), event_details);
- ui::EventSource* event_source =
- ash::Shell::GetPrimaryRootWindow()->GetHost()->GetEventSource();
- ui::EventSourceTestApi event_source_test(event_source);
- ui::EventDispatchDetails details =
- event_source_test.SendEventToSink(&gesture_event);
- CHECK(!details.dispatcher_destroyed);
- }
- class TestExtendedDragSourceDelegate : public ExtendedDragSource::Delegate {
- public:
- TestExtendedDragSourceDelegate(bool allow_drop_no_target, bool lock_cursor)
- : allow_drap_no_target_(allow_drop_no_target),
- lock_cursor_(lock_cursor) {}
- TestExtendedDragSourceDelegate(const TestExtendedDragSourceDelegate&) =
- delete;
- TestExtendedDragSourceDelegate& operator=(
- const TestExtendedDragSourceDelegate&) = delete;
- ~TestExtendedDragSourceDelegate() override = default;
- // ExtendedDragSource::Delegate:
- bool ShouldAllowDropAnywhere() const override {
- return allow_drap_no_target_;
- }
- bool ShouldLockCursor() const override { return lock_cursor_; }
- void OnSwallowed(const std::string& mime_type) override {
- ASSERT_FALSE(swallowed_);
- swallowed_ = true;
- }
- void OnUnswallowed(const std::string& mime_type,
- const gfx::Vector2d& offset) override {
- ASSERT_TRUE(swallowed_);
- swallowed_ = false;
- }
- void OnDataSourceDestroying() override { delete this; }
- private:
- const bool allow_drap_no_target_;
- const bool lock_cursor_;
- bool swallowed_ = true;
- };
- class ExtendedDragSourceTest : public test::ExoTestBase {
- public:
- ExtendedDragSourceTest() {}
- ExtendedDragSourceTest(const ExtendedDragSourceTest&) = delete;
- ExtendedDragSourceTest& operator=(const ExtendedDragSourceTest&) = delete;
- ~ExtendedDragSourceTest() override = default;
- void SetUp() override {
- test::ExoTestBase::SetUp();
- drag_drop_controller_ = static_cast<ash::DragDropController*>(
- aura::client::GetDragDropClient(ash::Shell::GetPrimaryRootWindow()));
- ASSERT_TRUE(drag_drop_controller_);
- drag_drop_controller_->set_should_block_during_drag_drop(false);
- drag_drop_controller_->set_enabled(true);
- seat_ =
- std::make_unique<Seat>(std::make_unique<TestDataExchangeDelegate>());
- data_source_delegate_ = std::make_unique<TestDataSourceDelegate>();
- data_source_ = std::make_unique<DataSource>(data_source_delegate_.get());
- extended_drag_source_ = std::make_unique<ExtendedDragSource>(
- data_source_.get(), new TestExtendedDragSourceDelegate(
- /*allow_drop_no_target=*/true,
- /*lock_cursor=*/true));
- }
- void TearDown() override {
- extended_drag_source_.reset();
- data_source_.reset();
- data_source_delegate_.reset();
- seat_.reset();
- test::ExoTestBase::TearDown();
- }
- protected:
- void StartExtendedDragSession(aura::Window* origin,
- gfx::Point start_location,
- int operation,
- ui::mojom::DragEventSource source) {
- auto data = std::make_unique<ui::OSExchangeData>();
- data->SetString(u"I am being dragged");
- drag_drop_controller_->set_toplevel_window_drag_delegate(
- extended_drag_source_.get());
- drag_drop_controller_->StartDragAndDrop(std::move(data),
- origin->GetRootWindow(), origin,
- start_location, operation, source);
- }
- std::unique_ptr<Buffer> CreateBuffer(gfx::Size size) {
- return std::make_unique<Buffer>(
- exo_test_helper()->CreateGpuMemoryBuffer(size));
- }
- ash::DragDropController* drag_drop_controller_ = nullptr;
- std::unique_ptr<Seat> seat_;
- std::unique_ptr<DataSource> data_source_;
- std::unique_ptr<ExtendedDragSource> extended_drag_source_;
- std::unique_ptr<TestDataSourceDelegate> data_source_delegate_;
- };
- // Enables or disables tablet mode and waits for the transition to finish.
- void SetTabletModeEnabled(bool enabled) {
- // This does not use ShellTestApi or TabletModeControllerTestApi because those
- // are implemented in test-only files.
- ash::TabletMode::Waiter waiter(enabled);
- ash::Shell::Get()->tablet_mode_controller()->SetEnabledForTest(enabled);
- waiter.Wait();
- }
- } // namespace
- TEST_F(ExtendedDragSourceTest, DestroySource) {
- Surface origin;
- // Give |origin| a root window and start DragDropOperation.
- GetContext()->AddChild(origin.window());
- seat_->StartDrag(data_source_.get(), &origin,
- /*icon=*/nullptr, ui::mojom::DragEventSource::kMouse);
- // Ensure that destroying the data source invalidates its extended_drag_source
- // counterpart for the rest of its lifetime.
- EXPECT_TRUE(seat_->get_drag_drop_operation_for_testing());
- EXPECT_TRUE(extended_drag_source_->IsActive());
- data_source_.reset();
- EXPECT_FALSE(seat_->get_drag_drop_operation_for_testing());
- EXPECT_FALSE(extended_drag_source_->IsActive());
- }
- TEST_F(ExtendedDragSourceTest, DragSurfaceAlreadyMapped) {
- // Create and map a toplevel shell surface.
- auto surface = std::make_unique<Surface>();
- auto shell_surface = std::make_unique<ShellSurface>(surface.get());
- auto buffer = CreateBuffer({32, 32});
- surface->Attach(buffer.get());
- surface->Commit();
- gfx::Point origin(0, 0);
- shell_surface->GetWidget()->SetBounds(gfx::Rect(origin, buffer->GetSize()));
- EXPECT_EQ(origin, surface->window()->GetBoundsInRootWindow().origin());
- // Set it as the dragged surface when it's already mapped. This allows clients
- // to set existing/visible windows as the dragged surface and possibly
- // snapping it to another surface, which is required for Chrome's tab drag use
- // case, for example.
- aura::Window* window = shell_surface->GetWidget()->GetNativeWindow();
- extended_drag_source_->Drag(surface.get(), gfx::Vector2d());
- EXPECT_EQ(window, extended_drag_source_->GetDraggedWindowForTesting());
- EXPECT_TRUE(extended_drag_source_->GetDragOffsetForTesting().has_value());
- EXPECT_EQ(gfx::Vector2d(0, 0),
- *extended_drag_source_->GetDragOffsetForTesting());
- // Start the DND + extended-drag session.
- // Creates a mouse-pressed event before starting the drag session.
- ui::test::EventGenerator generator(GetContext(), gfx::Point(10, 10));
- generator.PressLeftButton();
- StartExtendedDragSession(window, gfx::Point(0, 0),
- ui::DragDropTypes::DRAG_MOVE,
- ui::mojom::DragEventSource::kMouse);
- // Verify that dragging it by 190,190, with the current pointer location being
- // 10,10 will set the dragged window bounds as expected.
- generator.MoveMouseBy(190, 190);
- generator.ReleaseLeftButton();
- EXPECT_EQ(gfx::Point(200, 200), window->GetBoundsInScreen().origin());
- }
- // This class installs an observer to the window being dragged.
- // The goal is to ensure the drag 'n drop only effectively starts
- // off of the aura::WindowObserver::OnWindowVisibilityChanged() hook,
- // when it is guarantee the its state is properly set.
- class WindowObserverHookChecker : public aura::WindowObserver {
- public:
- explicit WindowObserverHookChecker(aura::Window* surface_window)
- : surface_window_(surface_window) {
- DCHECK(!surface_window_->GetRootWindow());
- surface_window_->AddObserver(this);
- }
- ~WindowObserverHookChecker() {
- DCHECK(dragged_window_);
- dragged_window_->RemoveObserver(this);
- }
- void OnWindowAddedToRootWindow(aura::Window* window) override {
- dragged_window_ = surface_window_->GetToplevelWindow();
- dragged_window_->AddObserver(this);
- surface_window_->RemoveObserver(this);
- dragged_window_->SetProperty(aura::client::kAppType,
- static_cast<int>(ash::AppType::LACROS));
- }
- MOCK_METHOD(void,
- OnWindowVisibilityChanging,
- (aura::Window*, bool),
- (override));
- MOCK_METHOD(void,
- OnWindowVisibilityChanged,
- (aura::Window*, bool),
- (override));
- private:
- aura::Window* surface_window_ = nullptr;
- aura::Window* dragged_window_ = nullptr;
- };
- // Differently than the window observer class above, this one observers
- // the window instance being directly provided to its ctor.
- class WindowObserverHookChecker2 : public aura::WindowObserver {
- public:
- explicit WindowObserverHookChecker2(aura::Window* surface_window)
- : surface_window_(surface_window) {
- surface_window_->AddObserver(this);
- }
- ~WindowObserverHookChecker2() { surface_window_->RemoveObserver(this); }
- MOCK_METHOD(void,
- OnWindowPropertyChanged,
- (aura::Window*, const void*, intptr_t),
- (override));
- private:
- aura::Window* surface_window_ = nullptr;
- };
- TEST_F(ExtendedDragSourceTest, DragSurfaceNotMappedYet) {
- // Create and Map the drag origin surface
- auto surface = std::make_unique<Surface>();
- auto shell_surface = std::make_unique<ShellSurface>(surface.get());
- auto buffer = CreateBuffer({32, 32});
- surface->Attach(buffer.get());
- surface->Commit();
- // Creates a mouse-pressed event before starting the drag session.
- ui::test::EventGenerator generator(GetContext(), gfx::Point(10, 10));
- generator.PressLeftButton();
- // Start the DND + extended-drag session.
- StartExtendedDragSession(shell_surface->GetWidget()->GetNativeWindow(),
- gfx::Point(0, 0), ui::DragDropTypes::DRAG_MOVE,
- ui::mojom::DragEventSource::kMouse);
- // Create a new surface to emulate a "detachment" process.
- auto detached_surface = std::make_unique<Surface>();
- auto detached_shell_surface =
- std::make_unique<ShellSurface>(detached_surface.get());
- // Set |surface| as the dragged surface while it's still unmapped/invisible.
- // This can be used to implement tab detaching in Chrome's tab drag use case,
- // for example. Extended drag source will monitor surface mapping and it's
- // expected to position it correctly using the provided drag offset here
- // relative to the current pointer location.
- extended_drag_source_->Drag(detached_surface.get(), gfx::Vector2d(10, 10));
- EXPECT_FALSE(extended_drag_source_->GetDraggedWindowForTesting());
- EXPECT_TRUE(extended_drag_source_->GetDragOffsetForTesting().has_value());
- EXPECT_EQ(gfx::Vector2d(10, 10),
- *extended_drag_source_->GetDragOffsetForTesting());
- // Ensure drag 'n drop starts after
- // ExtendedDragSource::OnDraggedWindowVisibilityChanged()
- aura::Window* toplevel_window;
- WindowObserverHookChecker checker(detached_surface->window());
- EXPECT_CALL(checker, OnWindowVisibilityChanging(_, _))
- .Times(1)
- .WillOnce(DoAll(
- SaveArg<0>(&toplevel_window), InvokeWithoutArgs([&]() {
- auto* toplevel_handler =
- ash::Shell::Get()->toplevel_window_event_handler();
- EXPECT_FALSE(toplevel_handler->is_drag_in_progress());
- EXPECT_TRUE(toplevel_window->GetProperty(ash::kIsDraggingTabsKey));
- })));
- EXPECT_CALL(checker, OnWindowVisibilityChanged(_, _))
- .Times(1)
- .WillOnce(InvokeWithoutArgs([]() {
- auto* toplevel_handler =
- ash::Shell::Get()->toplevel_window_event_handler();
- EXPECT_TRUE(toplevel_handler->is_drag_in_progress());
- }));
- // Map the |detached_surface|.
- auto detached_buffer = CreateBuffer({50, 50});
- detached_surface->Attach(detached_buffer.get());
- detached_surface->Commit();
- // Ensure the toplevel window for the dragged surface set above, is correctly
- // detected, after it's mapped.
- aura::Window* window = detached_shell_surface->GetWidget()->GetNativeWindow();
- EXPECT_TRUE(extended_drag_source_->GetDraggedWindowForTesting());
- EXPECT_EQ(window, extended_drag_source_->GetDraggedWindowForTesting());
- // Verify that dragging it by 100,100, with drag offset 10,10 and current
- // pointer location 50,50 will set the dragged window bounds as expected.
- generator.set_current_screen_location(gfx::Point(100, 100));
- generator.DragMouseBy(50, 50);
- generator.ReleaseLeftButton();
- EXPECT_EQ(gfx::Point(140, 140), window->GetBoundsInScreen().origin());
- }
- TEST_F(ExtendedDragSourceTest, DragSurfaceNotMappedYet_TabletMode) {
- SetTabletModeEnabled(true);
- // Create and Map the drag origin surface
- auto surface = std::make_unique<Surface>();
- auto shell_surface = std::make_unique<ShellSurface>(surface.get());
- auto buffer = CreateBuffer({32, 32});
- surface->Attach(buffer.get());
- surface->Commit();
- // Creates a mouse-pressed event before starting the drag session.
- ui::test::EventGenerator generator(GetContext(), gfx::Point(10, 10));
- generator.PressLeftButton();
- // Start the DND + extended-drag session.
- StartExtendedDragSession(shell_surface->GetWidget()->GetNativeWindow(),
- gfx::Point(0, 0), ui::DragDropTypes::DRAG_MOVE,
- ui::mojom::DragEventSource::kMouse);
- // Create a new surface to emulate a "detachment" process.
- auto detached_surface = std::make_unique<Surface>();
- auto detached_shell_surface =
- std::make_unique<ShellSurface>(detached_surface.get());
- // Set |surface| as the dragged surface while it's still unmapped/invisible.
- // This can be used to implement tab detaching in Chrome's tab drag use case,
- // for example. Extended drag source will monitor surface mapping and it's
- // expected to position it correctly using the provided drag offset here
- // relative to the current pointer location.
- extended_drag_source_->Drag(detached_surface.get(), gfx::Vector2d(10, 10));
- EXPECT_FALSE(extended_drag_source_->GetDraggedWindowForTesting());
- EXPECT_TRUE(extended_drag_source_->GetDragOffsetForTesting().has_value());
- EXPECT_EQ(gfx::Vector2d(10, 10),
- *extended_drag_source_->GetDragOffsetForTesting());
- // Ensure drag 'n drop starts after
- // ExtendedDragSource::OnDraggedWindowVisibilityChanged()
- aura::Window* toplevel_window = nullptr;
- WindowObserverHookChecker checker(detached_surface->window());
- EXPECT_CALL(checker, OnWindowVisibilityChanging(_, _))
- .Times(1)
- .WillOnce(DoAll(
- SaveArg<0>(&toplevel_window), InvokeWithoutArgs([&]() {
- auto* toplevel_handler =
- ash::Shell::Get()->toplevel_window_event_handler();
- EXPECT_FALSE(toplevel_handler->is_drag_in_progress());
- EXPECT_TRUE(toplevel_window->GetProperty(ash::kIsDraggingTabsKey));
- })));
- EXPECT_CALL(checker, OnWindowVisibilityChanged(_, _))
- .Times(1)
- .WillOnce(DoAll(
- SaveArg<0>(&toplevel_window), InvokeWithoutArgs([&]() {
- auto* toplevel_handler =
- ash::Shell::Get()->toplevel_window_event_handler();
- EXPECT_TRUE(toplevel_handler->is_drag_in_progress());
- gfx::Rect* override_bounds =
- toplevel_window->GetProperty(ash::kRestoreBoundsOverrideKey);
- EXPECT_TRUE(override_bounds && !override_bounds->IsEmpty());
- EXPECT_EQ(*override_bounds, toplevel_window->bounds());
- })));
- // Map the |detached_surface|.
- auto detached_buffer = CreateBuffer({50, 50});
- detached_surface->Attach(detached_buffer.get());
- detached_surface->Commit();
- // Ensure the toplevel window for the dragged surface set above, is correctly
- // detected, after it's mapped.
- aura::Window* window = detached_shell_surface->GetWidget()->GetNativeWindow();
- EXPECT_TRUE(extended_drag_source_->GetDraggedWindowForTesting());
- EXPECT_EQ(window, extended_drag_source_->GetDraggedWindowForTesting());
- WindowObserverHookChecker2 checker2(
- shell_surface->GetWidget()->GetNativeWindow());
- aura::Window* source_window = nullptr;
- const void* property_key;
- EXPECT_CALL(checker2, OnWindowPropertyChanged(_, _, _))
- .Times(AnyNumber())
- .WillRepeatedly(
- DoAll(SaveArg<0>(&source_window), SaveArg<1>(&property_key),
- InvokeWithoutArgs([&]() {
- if (property_key ==
- chromeos::kIsDeferredTabDraggingTargetWindowKey) {
- bool new_value = source_window->GetProperty(
- chromeos::kIsDeferredTabDraggingTargetWindowKey);
- EXPECT_TRUE(new_value);
- }
- })));
- generator.ReleaseLeftButton();
- SetTabletModeEnabled(false);
- }
- TEST_F(ExtendedDragSourceTest, DragSurfaceNotMappedYet_Touch) {
- // Create and Map the drag origin surface
- auto surface = std::make_unique<Surface>();
- auto shell_surface = std::make_unique<ShellSurface>(surface.get());
- auto buffer = CreateBuffer({32, 32});
- surface->Attach(buffer.get());
- surface->Commit();
- // Start the DND + extended-drag session.
- StartExtendedDragSession(shell_surface->GetWidget()->GetNativeWindow(),
- gfx::Point(0, 0), ui::DragDropTypes::DRAG_MOVE,
- ui::mojom::DragEventSource::kTouch);
- // Create a new surface to emulate a "detachment" process.
- auto detached_surface = std::make_unique<Surface>();
- auto detached_shell_surface =
- std::make_unique<ShellSurface>(detached_surface.get());
- // Set |surface| as the dragged surface while it's still unmapped/invisible.
- // This can be used to implement tab detaching in Chrome's tab drag use case,
- // for example. Extended drag source will monitor surface mapping and it's
- // expected to position it correctly using the provided drag offset here
- // relative to the current pointer location.
- extended_drag_source_->Drag(detached_surface.get(), gfx::Vector2d(10, 10));
- EXPECT_FALSE(extended_drag_source_->GetDraggedWindowForTesting());
- EXPECT_TRUE(extended_drag_source_->GetDragOffsetForTesting().has_value());
- EXPECT_EQ(gfx::Vector2d(10, 10),
- *extended_drag_source_->GetDragOffsetForTesting());
- // Initiate the gesture sequence.
- DispatchGesture(ui::ET_GESTURE_BEGIN, gfx::Point(10, 10));
- // Map the |detached_surface|.
- auto detached_buffer = CreateBuffer({50, 50});
- detached_surface->Attach(detached_buffer.get());
- detached_surface->Commit();
- // Ensure the toplevel window for the dragged surface set above, is correctly
- // detected, after it's mapped.
- aura::Window* window = detached_shell_surface->GetWidget()->GetNativeWindow();
- EXPECT_TRUE(extended_drag_source_->GetDraggedWindowForTesting());
- EXPECT_EQ(window, extended_drag_source_->GetDraggedWindowForTesting());
- // Verify that dragging it by 100,100, with drag offset 10,10 and current
- // pointer location 50,50 will set the dragged window bounds as expected.
- ui::test::EventGenerator generator(GetContext());
- generator.set_current_screen_location(gfx::Point(100, 100));
- generator.PressMoveAndReleaseTouchBy(50, 50);
- EXPECT_EQ(gfx::Point(140, 140), window->GetBoundsInScreen().origin());
- }
- TEST_F(ExtendedDragSourceTest, DestroyDraggedSurfaceWhileDragging) {
- // Create and map a toplevel shell surface.
- gfx::Size buffer_size(32, 32);
- std::unique_ptr<Buffer> buffer(
- new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
- std::unique_ptr<Surface> surface(new Surface);
- std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get()));
- surface->Attach(buffer.get());
- surface->Commit();
- gfx::Point origin(0, 0);
- shell_surface->GetWidget()->SetBounds(gfx::Rect(origin, buffer_size));
- EXPECT_EQ(origin, surface->window()->GetBoundsInRootWindow().origin());
- // Start dragging |surface|'s window.
- extended_drag_source_->Drag(surface.get(), gfx::Vector2d());
- aura::Window* window = shell_surface->GetWidget()->GetNativeWindow();
- EXPECT_EQ(window, extended_drag_source_->GetDraggedWindowForTesting());
- // Make sure extended drag source gracefully handles window destruction during
- // while the drag session is still alive.
- shell_surface.reset();
- EXPECT_TRUE(surface->window()->GetBoundsInScreen().origin().IsOrigin());
- ui::test::EventGenerator generator(GetContext());
- generator.DragMouseBy(190, 190);
- EXPECT_TRUE(surface->window()->GetBoundsInScreen().origin().IsOrigin());
- }
- // Regression test for crbug.com/1330125.
- // In exo, the drag source window could be destroyed while the dragged window
- // is updating its visibility. The example in this bug was while merging a
- // single-tab browser window in and out of another browser window.
- TEST_F(ExtendedDragSourceTest, DestroyDragSourceWindowWhileDragging) {
- auto shell_surface =
- exo::test::ShellSurfaceBuilder({32, 32}).BuildShellSurface();
- auto* surface = shell_surface->root_surface();
- // Start hidden so when it's shown later it triggers the memory violation.
- shell_surface->GetWidget()->Hide();
- extended_drag_source_->Drag(surface, gfx::Vector2d());
- aura::Window* window = shell_surface->GetWidget()->GetNativeWindow();
- EXPECT_EQ(window, extended_drag_source_->GetDraggedWindowForTesting());
- auto drag_source_window = CreateToplevelTestWindow({20, 30, 200, 100});
- extended_drag_source_->OnToplevelWindowDragStarted(
- {70.0, 70.0}, ui::mojom::DragEventSource::kMouse,
- drag_source_window.get());
- EXPECT_EQ(extended_drag_source_->GetDragSourceWindowForTesting(),
- drag_source_window.get());
- drag_source_window.reset();
- EXPECT_EQ(extended_drag_source_->GetDragSourceWindowForTesting(), nullptr);
- // Without the fix, in asan build, this should cause the use-after-free.
- shell_surface->GetWidget()->Show();
- }
- TEST_F(ExtendedDragSourceTest, DragRequestsInRow_NoCrash) {
- // Create and map a toplevel shell surface, but hidden.
- auto shell_surface =
- exo::test::ShellSurfaceBuilder({32, 32}).BuildShellSurface();
- auto* surface = shell_surface->root_surface();
- shell_surface->GetWidget()->Hide();
- // Request two dragging |surface|'s actions in a roll, which a real
- // world use case scenario when system is under heavy load.
- extended_drag_source_->Drag(surface, gfx::Vector2d());
- extended_drag_source_->Drag(surface, gfx::Vector2d());
- // Make sure extended drag source gracefully handles window destruction during
- // while the drag session is still alive.
- shell_surface.reset();
- }
- TEST_F(ExtendedDragSourceTest, CancelDraggingOperation) {
- // Create and map a toplevel shell surface.
- auto shell_surface =
- exo::test::ShellSurfaceBuilder({32, 32}).BuildShellSurface();
- auto* surface = shell_surface->root_surface();
- extended_drag_source_->Drag(surface, gfx::Vector2d());
- // Start the DND + extended-drag session.
- // Creates a mouse-pressed event before starting the drag session.
- ui::test::EventGenerator generator(GetContext(), gfx::Point(10, 10));
- generator.PressLeftButton();
- // Start a DragDropOperation.
- drag_drop_controller_->set_should_block_during_drag_drop(true);
- seat_->StartDrag(data_source_.get(), surface, /*icon=*/nullptr,
- ui::mojom::DragEventSource::kMouse);
- auto task_1 = base::BindLambdaForTesting([&]() {
- generator.MoveMouseBy(190, 190);
- auto* toplevel_handler = ash::Shell::Get()->toplevel_window_event_handler();
- EXPECT_TRUE(toplevel_handler->is_drag_in_progress());
- drag_drop_controller_->DragCancel();
- EXPECT_FALSE(toplevel_handler->is_drag_in_progress());
- });
- base::ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, base::BindOnce(std::move(task_1)));
- base::RunLoop().RunUntilIdle();
- EXPECT_TRUE(data_source_delegate_->cancelled());
- }
- // Make sure that the extended drag is recognized as shell surface drag.
- TEST_F(ExtendedDragSourceTest, DragWithScreenCoordinates) {
- auto shell_surface = test::ShellSurfaceBuilder({20, 20}).BuildShellSurface();
- TestDataExchangeDelegate data_exchange_delegate;
- auto delegate = std::make_unique<TestDataSourceDelegate>();
- auto data_source = std::make_unique<DataSource>(delegate.get());
- constexpr char kTextMimeType[] = "text/plain";
- data_source->Offer(kTextMimeType);
- gfx::PointF location(10, 10);
- auto operation = DragDropOperation::Create(
- &data_exchange_delegate, data_source.get(), shell_surface->root_surface(),
- nullptr, location, ui::mojom::DragEventSource::kMouse);
- auto* drag_drop_controller = static_cast<ash::DragDropController*>(
- aura::client::GetDragDropClient(ash::Shell::GetPrimaryRootWindow()));
- EXPECT_FALSE(shell_surface->IsDragged());
- base::RunLoop loop;
- drag_drop_controller->SetLoopClosureForTesting(
- base::BindLambdaForTesting([&]() {
- // The drag session must have been started by the time
- // drag loop starts.
- EXPECT_TRUE(shell_surface->IsDragged());
- drag_drop_controller->DragCancel();
- loop.Quit();
- }),
- base::DoNothing());
- loop.Run();
- operation.reset();
- EXPECT_FALSE(shell_surface->IsDragged());
- }
- #if BUILDFLAG(IS_CHROMEOS_ASH)
- TEST_F(ExtendedDragSourceTest, DragWithScreenCoordinates_Touch) {
- // Create and map a toplevel shell surface.
- auto shell_surface =
- exo::test::ShellSurfaceBuilder({32, 32}).BuildShellSurface();
- auto* surface = shell_surface->root_surface();
- extended_drag_source_->Drag(surface, gfx::Vector2d());
- // Start the DND + extended-drag session.
- // Creates a mouse-pressed event before starting the drag session.
- ui::test::EventGenerator generator(GetContext(), gfx::Point(10, 10));
- generator.MoveTouch(surface->window()->GetBoundsInScreen().origin());
- generator.PressTouch();
- // Start a DragDropOperation.
- drag_drop_controller_->set_should_block_during_drag_drop(true);
- seat_->StartDrag(data_source_.get(), surface, /*icon=*/nullptr,
- ui::mojom::DragEventSource::kTouch);
- base::RunLoop loop;
- drag_drop_controller_->SetLoopClosureForTesting(
- base::BindLambdaForTesting([&]() {
- auto* toplevel_handler =
- ash::Shell::Get()->toplevel_window_event_handler();
- EXPECT_TRUE(toplevel_handler->is_drag_in_progress());
- auto* window_state =
- ash::WindowState::Get(surface->window()->GetToplevelWindow());
- auto* drag_details = window_state->drag_details();
- DCHECK(drag_details);
- EXPECT_EQ(gfx::ToRoundedPoint(drag_details->initial_location_in_parent),
- surface->window()->GetBoundsInScreen().origin());
- drag_drop_controller_->DragCancel();
- EXPECT_FALSE(toplevel_handler->is_drag_in_progress());
- loop.Quit();
- }),
- base::DoNothing());
- loop.Run();
- EXPECT_TRUE(data_source_delegate_->cancelled());
- }
- #endif // BUILDFLAG(IS_CHROMEOS_ASH)
- TEST_F(ExtendedDragSourceTest, DragToAnotherDisplay) {
- UpdateDisplay("400x300,800x600");
- const gfx::Rect kOriginalWindowBounds(410, 10, 500, 200);
- // Create and map a toplevel shell surface, with the size larger than 2nd
- // display to test if configure uses the adjusted size.
- auto shell_surface =
- exo::test::ShellSurfaceBuilder(kOriginalWindowBounds.size())
- .SetOrigin(kOriginalWindowBounds.origin())
- .BuildShellSurface();
- auto* surface = shell_surface->root_surface();
- EXPECT_EQ(kOriginalWindowBounds,
- shell_surface->GetWidget()->GetWindowBoundsInScreen());
- auto display = display::Screen::GetScreen()->GetDisplayNearestWindow(
- shell_surface->GetWidget()->GetNativeWindow());
- EXPECT_EQ(gfx::Rect(400, 0, 800, 600), display.bounds());
- uint32_t serial = 0;
- gfx::Rect drop_bounds;
- auto configure_callback = base::BindLambdaForTesting(
- [&](const gfx::Rect& bounds, chromeos::WindowStateType state_type,
- bool resizing, bool activated, const gfx::Vector2d& origin_offset) {
- drop_bounds = bounds;
- return ++serial;
- });
- std::vector<gfx::Point> origins;
- auto origin_change_callback = base::BindLambdaForTesting(
- [&](const gfx::Point& point) { origins.push_back(point); });
- // Map shell surface.
- shell_surface->set_configure_callback(configure_callback);
- shell_surface->set_origin_change_callback(origin_change_callback);
- constexpr int kDragOffset = 100;
- extended_drag_source_->Drag(surface, gfx::Vector2d(kDragOffset, 0));
- // Start the DND + extended-drag session.
- // Creates a mouse-pressed event before starting the drag session.
- ui::test::EventGenerator* generator = GetEventGenerator();
- generator->MoveMouseTo({410 + kDragOffset, 10});
- generator->PressLeftButton();
- // Start a DragDropOperation.
- drag_drop_controller_->set_should_block_during_drag_drop(true);
- seat_->StartDrag(data_source_.get(), surface, /*icon=*/nullptr,
- ui::mojom::DragEventSource::kMouse);
- // Just move to the middle to avoid snapping.
- int x_movement = 300;
- constexpr int kXDragDelta = 20;
- auto* toplevel_handler = ash::Shell::Get()->toplevel_window_event_handler();
- base::RunLoop loop;
- size_t move_count = 0;
- drag_drop_controller_->SetLoopClosureForTesting(
- base::BindLambdaForTesting([&]() {
- if (x_movement == 300) {
- auto* window_state = ash::WindowState::Get(
- shell_surface->GetWidget()->GetNativeWindow());
- EXPECT_EQ(gfx::PointF(110, 10),
- window_state->drag_details()->initial_location_in_parent);
- }
- if (x_movement > 0) {
- x_movement -= kXDragDelta;
- move_count++;
- generator->MoveMouseBy(-kXDragDelta, 0);
- EXPECT_TRUE(toplevel_handler->is_drag_in_progress());
- } else {
- generator->ReleaseLeftButton();
- }
- }),
- loop.QuitClosure());
- loop.Run();
- EXPECT_FALSE(toplevel_handler->is_drag_in_progress());
- EXPECT_FALSE(shell_surface->GetWidget()->IsMaximized());
- display = display::Screen::GetScreen()->GetDisplayNearestWindow(
- shell_surface->GetWidget()->GetNativeWindow());
- gfx::Size secondary_display_size(400, 300);
- EXPECT_EQ(gfx::Rect(secondary_display_size), display.bounds());
- EXPECT_EQ(move_count, origins.size());
- // Configure when dropped.
- EXPECT_EQ(1u, serial);
- // Upon drop, the window is shrunk horizontally.
- gfx::Rect expected_drop_bounds =
- gfx::Rect(origins.back(), kOriginalWindowBounds.size());
- expected_drop_bounds.set_width(secondary_display_size.width());
- int offset =
- (kOriginalWindowBounds.width() - expected_drop_bounds.width()) / 2;
- expected_drop_bounds.set_x(expected_drop_bounds.x() + offset);
- EXPECT_EQ(expected_drop_bounds, drop_bounds);
- EXPECT_EQ(expected_drop_bounds,
- shell_surface->GetWidget()->GetWindowBoundsInScreen());
- // Make sure all origins are kXDragDelta apart.
- for (size_t i = 1; origins.size() < i; i++) {
- EXPECT_EQ(origins[i - 1].x() + kXDragDelta, origins[i].x());
- }
- // The size will be adjusted when dropped.
- EXPECT_EQ(gfx::Size(400, 200), drop_bounds.size());
- }
- } // namespace exo
|