| // Copyright 2020 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ash/drag_drop/tab_drag_drop_delegate.h" |
| |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "ash/constants/ash_features.h" |
| #include "ash/public/cpp/new_window_delegate.h" |
| #include "ash/public/cpp/test/test_new_window_delegate.h" |
| #include "ash/screen_util.h" |
| #include "ash/shell.h" |
| #include "ash/test/ash_test_base.h" |
| #include "ash/test_shell_delegate.h" |
| #include "ash/wm/overview/overview_controller.h" |
| #include "ash/wm/overview/overview_test_util.h" |
| #include "ash/wm/splitview/split_view_controller.h" |
| #include "ash/wm/tablet_mode/tablet_mode_controller_test_api.h" |
| #include "ash/wm/window_util.h" |
| #include "base/containers/contains.h" |
| #include "base/containers/flat_map.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/pickle.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/test/bind.h" |
| #include "base/test/gmock_callback_support.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "ui/aura/client/aura_constants.h" |
| #include "ui/base/clipboard/clipboard_format_type.h" |
| #include "ui/base/clipboard/custom_data_helper.h" |
| #include "ui/base/dragdrop/os_exchange_data.h" |
| #include "ui/compositor/layer.h" |
| #include "ui/compositor/test/test_utils.h" |
| #include "ui/events/test/event_generator.h" |
| #include "ui/gfx/geometry/vector2d.h" |
| #include "ui/wm/core/scoped_animation_disabler.h" |
| #include "ui/wm/core/window_util.h" |
| |
| using ::base::test::RunOnceCallback; |
| using ::testing::_; |
| using ::testing::NiceMock; |
| using ::testing::Return; |
| |
| namespace ash { |
| |
| namespace { |
| |
| constexpr int kWebUITabStripHeight = 100; |
| |
| class MockShellDelegate : public TestShellDelegate { |
| public: |
| MockShellDelegate() = default; |
| ~MockShellDelegate() override = default; |
| |
| MOCK_METHOD(bool, IsTabDrag, (const ui::OSExchangeData&), (override)); |
| |
| int GetBrowserWebUITabStripHeight() override { return kWebUITabStripHeight; } |
| }; |
| |
| class MockNewWindowDelegate : public TestNewWindowDelegate { |
| public: |
| MockNewWindowDelegate() = default; |
| ~MockNewWindowDelegate() override = default; |
| |
| MOCK_METHOD(void, |
| NewWindowForDetachingTab, |
| (aura::Window*, |
| const ui::OSExchangeData&, |
| NewWindowForDetachingTabCallback), |
| (override)); |
| }; |
| |
| } // namespace |
| |
| class TabDragDropDelegateTest : public AshTestBase { |
| public: |
| TabDragDropDelegateTest() = default; |
| |
| // AshTestBase: |
| void SetUp() override { |
| auto mock_new_window_delegate = |
| std::make_unique<NiceMock<MockNewWindowDelegate>>(); |
| mock_new_window_delegate_ptr_ = mock_new_window_delegate.get(); |
| test_new_window_delegate_provider_ = |
| std::make_unique<TestNewWindowDelegateProvider>( |
| std::move(mock_new_window_delegate)); |
| |
| auto mock_shell_delegate = std::make_unique<NiceMock<MockShellDelegate>>(); |
| mock_shell_delegate_ = mock_shell_delegate.get(); |
| AshTestBase::SetUp(std::move(mock_shell_delegate)); |
| ash::TabletModeControllerTestApi().EnterTabletMode(); |
| |
| // Create a dummy window and exit overview mode since drags can't be |
| // initiated from overview mode. |
| dummy_window_ = CreateToplevelTestWindow(); |
| ASSERT_TRUE(ExitOverview()); |
| } |
| |
| void TearDown() override { |
| // Must be deleted before AshTestBase's tear down. |
| dummy_window_.reset(); |
| |
| // Clear our pointer before the object is destroyed. |
| mock_shell_delegate_ = nullptr; |
| test_new_window_delegate_provider_.reset(); |
| AshTestBase::TearDown(); |
| } |
| |
| MockShellDelegate* mock_shell_delegate() { return mock_shell_delegate_; } |
| |
| MockNewWindowDelegate* mock_new_window_delegate() { |
| return static_cast<MockNewWindowDelegate*>( |
| NewWindowDelegate::GetInstance()); |
| } |
| |
| private: |
| raw_ptr<NiceMock<MockShellDelegate>> mock_shell_delegate_ = nullptr; |
| |
| std::unique_ptr<TestNewWindowDelegateProvider> |
| test_new_window_delegate_provider_; |
| raw_ptr<NiceMock<MockNewWindowDelegate>, DanglingUntriaged> |
| mock_new_window_delegate_ptr_ = nullptr; |
| |
| std::unique_ptr<aura::Window> dummy_window_; |
| }; |
| |
| TEST_F(TabDragDropDelegateTest, ForwardsDragCheckToShellDelegate) { |
| ON_CALL(*mock_shell_delegate(), IsTabDrag(_)).WillByDefault(Return(false)); |
| EXPECT_FALSE(TabDragDropDelegate::IsChromeTabDrag(ui::OSExchangeData())); |
| |
| ON_CALL(*mock_shell_delegate(), IsTabDrag(_)).WillByDefault(Return(true)); |
| EXPECT_TRUE(TabDragDropDelegate::IsChromeTabDrag(ui::OSExchangeData())); |
| } |
| |
| TEST_F(TabDragDropDelegateTest, DragToExistingTabStrip) { |
| // Create a fake source window. Its details don't matter. |
| std::unique_ptr<aura::Window> source_window = CreateToplevelTestWindow(); |
| |
| // A new window shouldn't be created in this case. |
| EXPECT_CALL(*mock_new_window_delegate(), NewWindowForDetachingTab(_, _, _)) |
| .Times(0); |
| |
| // Emulate a drag session whose drop target accepts the drop. In this |
| // case, TabDragDropDelegate::Drop() is not called. |
| TabDragDropDelegate delegate(Shell::GetPrimaryRootWindow(), |
| source_window.get(), gfx::Point(0, 0)); |
| delegate.DragUpdate(gfx::Point(1, 0)); |
| delegate.DragUpdate(gfx::Point(2, 0)); |
| |
| // Let |delegate| be destroyed without a Drop() call. |
| } |
| |
| TEST_F(TabDragDropDelegateTest, DragToNewWindow) { |
| // Create the source window. This should automatically fill the work area |
| // since we're in tablet mode. |
| std::unique_ptr<aura::Window> source_window = CreateToplevelTestWindow(); |
| |
| EXPECT_FALSE( |
| SplitViewController::Get(source_window.get())->InTabletSplitViewMode()); |
| |
| const gfx::Point drag_start_location = source_window->bounds().CenterPoint(); |
| // Emulate a drag session ending in a drop to a new window. |
| auto delegate = std::make_unique<TabDragDropDelegate>( |
| Shell::GetPrimaryRootWindow(), source_window.get(), drag_start_location); |
| delegate->DragUpdate(drag_start_location); |
| delegate->DragUpdate(drag_start_location + gfx::Vector2d(1, 0)); |
| delegate->DragUpdate(drag_start_location + gfx::Vector2d(2, 0)); |
| |
| // Check that a new window is requested. Assume the correct drop data |
| // is passed. Return the new window. |
| std::unique_ptr<aura::Window> new_window = CreateToplevelTestWindow(); |
| EXPECT_CALL(*mock_new_window_delegate(), |
| NewWindowForDetachingTab(source_window.get(), _, _)) |
| .Times(1) |
| .WillOnce(RunOnceCallback<2>(new_window.get())); |
| |
| delegate.release()->DropAndDeleteSelf( |
| drag_start_location + gfx::Vector2d(2, 0), ui::OSExchangeData()); |
| |
| EXPECT_FALSE( |
| SplitViewController::Get(source_window.get())->InTabletSplitViewMode()); |
| } |
| |
| // When a tab is dragged to the left/right side of the Web Contents. It should |
| // enter split view. |
| TEST_F(TabDragDropDelegateTest, DropOnEdgeEntersSplitView) { |
| // Create the source window. This should automatically fill the work area |
| // since we're in tablet mode. |
| std::unique_ptr<aura::Window> source_window = CreateToplevelTestWindow(); |
| |
| // We want to avoid entering overview mode between the delegate.Drop() |
| // call and |new_window|'s destruction. So we define it here before |
| // creating it. |
| std::unique_ptr<aura::Window> new_window; |
| |
| // Emulate a drag to the right edge of the screen. |
| const gfx::Point drag_start_location = source_window->bounds().CenterPoint(); |
| const gfx::Point drag_end_location = |
| screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer( |
| source_window.get()) |
| .right_center(); |
| |
| auto delegate = std::make_unique<TabDragDropDelegate>( |
| Shell::GetPrimaryRootWindow(), source_window.get(), drag_start_location); |
| delegate->DragUpdate(drag_start_location); |
| delegate->DragUpdate(drag_end_location); |
| |
| new_window = CreateToplevelTestWindow(); |
| EXPECT_CALL(*mock_new_window_delegate(), |
| NewWindowForDetachingTab(source_window.get(), _, _)) |
| .Times(1) |
| .WillOnce(RunOnceCallback<2>(new_window.get())); |
| |
| delegate.release()->DropAndDeleteSelf(drag_end_location, |
| ui::OSExchangeData()); |
| |
| SplitViewController* const split_view_controller = |
| SplitViewController::Get(source_window.get()); |
| EXPECT_TRUE(split_view_controller->InTabletSplitViewMode()); |
| EXPECT_EQ(new_window.get(), |
| split_view_controller->GetSnappedWindow(SnapPosition::kSecondary)); |
| EXPECT_EQ(source_window.get(), |
| split_view_controller->GetSnappedWindow(SnapPosition::kPrimary)); |
| } |
| |
| // When a tab is dragged to the left/right edge of the tab strip. It should not |
| // enter split view. |
| // https://crbug.com/1316070 |
| TEST_F(TabDragDropDelegateTest, DropOnEdgeShouldNotEnterSplitView) { |
| // Create the source window. This should automatically fill the work area |
| // since we're in tablet mode. |
| std::unique_ptr<aura::Window> source_window = CreateToplevelTestWindow(); |
| |
| // We want to avoid entering overview mode between the delegate.Drop() |
| // call and |new_window|'s destruction. So we define it here before |
| // creating it. |
| std::unique_ptr<aura::Window> new_window; |
| |
| // Emulate a drag to the right edge of the tab strip. It should not enter |
| // split view. |
| const gfx::Point drag_start_location = source_window->bounds().CenterPoint(); |
| const gfx::Point drag_end_location = |
| gfx::Point(source_window->bounds().right(), kWebUITabStripHeight * 0.5); |
| |
| auto delegate = std::make_unique<TabDragDropDelegate>( |
| Shell::GetPrimaryRootWindow(), source_window.get(), drag_start_location); |
| delegate->DragUpdate(drag_start_location); |
| delegate->DragUpdate(drag_end_location); |
| |
| new_window = CreateToplevelTestWindow(); |
| EXPECT_CALL(*mock_new_window_delegate(), |
| NewWindowForDetachingTab(source_window.get(), _, _)) |
| .Times(1) |
| .WillOnce(RunOnceCallback<2>(new_window.get())); |
| |
| delegate.release()->DropAndDeleteSelf(drag_end_location, |
| ui::OSExchangeData()); |
| |
| SplitViewController* const split_view_controller = |
| SplitViewController::Get(source_window.get()); |
| EXPECT_FALSE(split_view_controller->InTabletSplitViewMode()); |
| } |
| |
| TEST_F(TabDragDropDelegateTest, DropTabInSplitViewMode) { |
| // Enter tablet split view mode by snap the source window to the left. |
| std::unique_ptr<aura::Window> source_window = CreateToplevelTestWindow(); |
| SplitViewController* const split_view_controller = |
| SplitViewController::Get(source_window.get()); |
| split_view_controller->SnapWindow(source_window.get(), |
| SnapPosition::kPrimary); |
| EXPECT_TRUE(split_view_controller->InTabletSplitViewMode()); |
| // Snap another window to the right to make sure right split screen is not in |
| // overview mode. |
| std::unique_ptr<aura::Window> right_window = CreateToplevelTestWindow(); |
| split_view_controller->SnapWindow(right_window.get(), |
| SnapPosition::kSecondary); |
| |
| const gfx::Point drag_start_location = source_window->bounds().CenterPoint(); |
| auto area = |
| screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer( |
| source_window.get()); |
| |
| // Emulate a drag to the right side of the screen. |
| // |new_window1| should snap to the right split view. |
| gfx::Point drag_end_location_right(area.width() * 0.8, area.height() * 0.5); |
| auto delegate1 = std::make_unique<TabDragDropDelegate>( |
| Shell::GetPrimaryRootWindow(), source_window.get(), drag_start_location); |
| delegate1->DragUpdate(drag_start_location); |
| delegate1->DragUpdate(drag_end_location_right); |
| std::unique_ptr<aura::Window> new_window1 = CreateToplevelTestWindow(); |
| EXPECT_CALL(*mock_new_window_delegate(), |
| NewWindowForDetachingTab(source_window.get(), _, _)) |
| .Times(1) |
| .WillOnce(RunOnceCallback<2>(new_window1.get())); |
| delegate1.release()->DropAndDeleteSelf(drag_end_location_right, |
| ui::OSExchangeData()); |
| |
| EXPECT_TRUE(split_view_controller->InTabletSplitViewMode()); |
| EXPECT_EQ(new_window1.get(), |
| split_view_controller->GetSnappedWindow(SnapPosition::kSecondary)); |
| EXPECT_EQ(source_window.get(), |
| split_view_controller->GetSnappedWindow(SnapPosition::kPrimary)); |
| new_window1.reset(); // Close |new_window1|. |
| |
| // Emulate a drag to the left side of the screen. |
| // |new_window2| should snap to the left split view. |
| // |source_window| should go into overview mode. |
| gfx::Point drag_end_location_left(area.width() * 0.2, area.height() * 0.5); |
| auto delegate2 = std::make_unique<TabDragDropDelegate>( |
| Shell::GetPrimaryRootWindow(), source_window.get(), drag_start_location); |
| delegate2->DragUpdate(drag_start_location); |
| delegate2->DragUpdate(drag_end_location_left); |
| std::unique_ptr<aura::Window> new_window2 = CreateToplevelTestWindow(); |
| EXPECT_CALL(*mock_new_window_delegate(), |
| NewWindowForDetachingTab(source_window.get(), _, _)) |
| .Times(1) |
| .WillOnce(RunOnceCallback<2>(new_window2.get())); |
| delegate2.release()->DropAndDeleteSelf(drag_end_location_left, |
| ui::OSExchangeData()); |
| |
| EXPECT_TRUE(split_view_controller->InTabletSplitViewMode()); |
| EXPECT_EQ(nullptr, |
| split_view_controller->GetSnappedWindow(SnapPosition::kSecondary)); |
| EXPECT_EQ(new_window2.get(), |
| split_view_controller->GetSnappedWindow(SnapPosition::kPrimary)); |
| ASSERT_TRUE(Shell::Get()->overview_controller()->InOverviewSession()); |
| EXPECT_TRUE( |
| base::Contains(GetWindowsListInOverviewGrids(), source_window.get())); |
| } |
| |
| TEST_F(TabDragDropDelegateTest, DropTabToOverviewMode) { |
| // Enter tablet split view mode by snap the source window to the left. |
| std::unique_ptr<aura::Window> source_window = CreateToplevelTestWindow(); |
| SplitViewController* const split_view_controller = |
| SplitViewController::Get(source_window.get()); |
| split_view_controller->SnapWindow(source_window.get(), |
| SnapPosition::kPrimary); |
| EXPECT_TRUE(split_view_controller->InTabletSplitViewMode()); |
| ASSERT_TRUE(Shell::Get()->overview_controller()->InOverviewSession()); |
| |
| const gfx::Point drag_start_location = source_window->bounds().CenterPoint(); |
| auto area = |
| screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer( |
| source_window.get()); |
| |
| // Emulate a drag to the right side of the screen. |
| // |new_window1| should snap to overview mode. |
| gfx::Point drag_end_location_right(area.width() * 0.8, area.height() * 0.5); |
| auto delegate1 = std::make_unique<TabDragDropDelegate>( |
| Shell::GetPrimaryRootWindow(), source_window.get(), drag_start_location); |
| delegate1->DragUpdate(drag_start_location); |
| delegate1->DragUpdate(drag_end_location_right); |
| std::unique_ptr<aura::Window> new_window = CreateToplevelTestWindow(); |
| EXPECT_CALL(*mock_new_window_delegate(), |
| NewWindowForDetachingTab(source_window.get(), _, _)) |
| .Times(1) |
| .WillOnce(RunOnceCallback<2>(new_window.get())); |
| delegate1.release()->DropAndDeleteSelf(drag_end_location_right, |
| ui::OSExchangeData()); |
| |
| EXPECT_EQ(nullptr, |
| split_view_controller->GetSnappedWindow(SnapPosition::kSecondary)); |
| EXPECT_TRUE( |
| base::Contains(GetWindowsListInOverviewGrids(), new_window.get())); |
| } |
| |
| TEST_F(TabDragDropDelegateTest, WillNotDropTabToOverviewModeInSnappingZone) { |
| // Enter tablet split view mode by snap the source window to the left. |
| std::unique_ptr<aura::Window> source_window = CreateToplevelTestWindow(); |
| SplitViewController* const split_view_controller = |
| SplitViewController::Get(source_window.get()); |
| split_view_controller->SnapWindow(source_window.get(), |
| SnapPosition::kPrimary); |
| EXPECT_TRUE(split_view_controller->InTabletSplitViewMode()); |
| ASSERT_TRUE(Shell::Get()->overview_controller()->InOverviewSession()); |
| |
| const gfx::Point drag_start_location = source_window->bounds().CenterPoint(); |
| auto area = |
| screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer( |
| source_window.get()); |
| |
| // Emulate a drag to the right snapping zone of the screen. |
| // |new_window1| should not snap to overview mode. |
| gfx::Point drag_end_location_right(area.width() * 0.95, area.height() * 0.5); |
| auto delegate1 = std::make_unique<TabDragDropDelegate>( |
| Shell::GetPrimaryRootWindow(), source_window.get(), drag_start_location); |
| delegate1->DragUpdate(drag_start_location); |
| delegate1->DragUpdate(drag_end_location_right); |
| std::unique_ptr<aura::Window> new_window = CreateToplevelTestWindow(); |
| EXPECT_CALL(*mock_new_window_delegate(), |
| NewWindowForDetachingTab(source_window.get(), _, _)) |
| .Times(1) |
| .WillOnce(RunOnceCallback<2>(new_window.get())); |
| delegate1.release()->DropAndDeleteSelf(drag_end_location_right, |
| ui::OSExchangeData()); |
| |
| EXPECT_EQ(new_window.get(), |
| split_view_controller->GetSnappedWindow(SnapPosition::kSecondary)); |
| ASSERT_FALSE(Shell::Get()->overview_controller()->InOverviewSession()); |
| } |
| |
| TEST_F(TabDragDropDelegateTest, WillNotDropTabToOverviewMode) { |
| // Enter tablet split view mode by snap the source window to the left. |
| std::unique_ptr<aura::Window> source_window = CreateToplevelTestWindow(); |
| SplitViewController* const split_view_controller = |
| SplitViewController::Get(source_window.get()); |
| split_view_controller->SnapWindow(source_window.get(), |
| SnapPosition::kPrimary); |
| EXPECT_TRUE(split_view_controller->InTabletSplitViewMode()); |
| ASSERT_TRUE(Shell::Get()->overview_controller()->InOverviewSession()); |
| |
| const gfx::Point drag_start_location = source_window->bounds().CenterPoint(); |
| auto area = |
| screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer( |
| source_window.get()); |
| |
| // Emulate a drag to the left side of the screen. |
| // |new_window1| should not snap to overview mode. |
| gfx::Point drag_end_location_right(area.width() * 0.2, area.height() * 0.5); |
| auto delegate1 = std::make_unique<TabDragDropDelegate>( |
| Shell::GetPrimaryRootWindow(), source_window.get(), drag_start_location); |
| delegate1->DragUpdate(drag_start_location); |
| delegate1->DragUpdate(drag_end_location_right); |
| std::unique_ptr<aura::Window> new_window = CreateToplevelTestWindow(); |
| EXPECT_CALL(*mock_new_window_delegate(), |
| NewWindowForDetachingTab(source_window.get(), _, _)) |
| .Times(1) |
| .WillOnce(RunOnceCallback<2>(new_window.get())); |
| delegate1.release()->DropAndDeleteSelf(drag_end_location_right, |
| ui::OSExchangeData()); |
| |
| EXPECT_EQ(new_window.get(), |
| split_view_controller->GetSnappedWindow(SnapPosition::kPrimary)); |
| EXPECT_FALSE( |
| base::Contains(GetWindowsListInOverviewGrids(), new_window.get())); |
| } |
| |
| TEST_F(TabDragDropDelegateTest, SourceWindowBoundsUpdatedWhileDragging) { |
| // Create the source window. This should automatically fill the work area |
| // since we're in tablet mode. |
| std::unique_ptr<aura::Window> source_window = CreateToplevelTestWindow(); |
| const gfx::Rect original_bounds = source_window->bounds(); |
| |
| // Drag a few pixels away to trigger window scaling, then to the |
| // screen edge to visually snap the source window. |
| const gfx::Point drag_start_location = source_window->bounds().CenterPoint(); |
| const gfx::Point drag_mid_location = |
| drag_start_location + gfx::Vector2d(10, 0); |
| const gfx::Point drag_end_location = |
| screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer( |
| source_window.get()) |
| .left_center(); |
| |
| { |
| TabDragDropDelegate delegate(Shell::GetPrimaryRootWindow(), |
| source_window.get(), drag_start_location); |
| delegate.DragUpdate(drag_start_location); |
| delegate.DragUpdate(drag_mid_location); |
| |
| // |source_window| should be shrunk in all directions |
| EXPECT_GT(source_window->bounds().x(), original_bounds.x()); |
| EXPECT_GT(source_window->bounds().y(), original_bounds.y()); |
| EXPECT_LT(source_window->bounds().right(), original_bounds.right()); |
| EXPECT_LT(source_window->bounds().bottom(), original_bounds.bottom()); |
| |
| delegate.DragUpdate(drag_end_location); |
| |
| // |source_window| should appear in the snapped position, but not |
| // actually be snapped. |
| SplitViewController* const split_view_controller = |
| SplitViewController::Get(source_window.get()); |
| EXPECT_EQ(source_window->bounds(), |
| split_view_controller->GetSnappedWindowBoundsInParent( |
| SnapPosition::kSecondary, source_window.get(), |
| chromeos::kDefaultSnapRatio)); |
| EXPECT_FALSE(split_view_controller->InSplitViewMode()); |
| } |
| |
| // The original bounds should be restored. |
| EXPECT_EQ(source_window->bounds(), original_bounds); |
| } |
| |
| TEST_F(TabDragDropDelegateTest, SnappedSourceWindowNotMoved) { |
| // Create the source window. This should automatically fill the work area |
| // since we're in tablet mode. |
| std::unique_ptr<aura::Window> source_window = CreateToplevelTestWindow(); |
| |
| SplitViewController* const split_view_controller = |
| SplitViewController::Get(source_window.get()); |
| SnapPosition const snap_position = SnapPosition::kPrimary; |
| split_view_controller->SnapWindow(source_window.get(), snap_position); |
| const gfx::Rect original_bounds = source_window->bounds(); |
| |
| const gfx::Point drag_start_location = source_window->bounds().CenterPoint(); |
| const gfx::Point drag_end_location = |
| drag_start_location + gfx::Vector2d(10, 0); |
| |
| { |
| TabDragDropDelegate delegate(Shell::GetPrimaryRootWindow(), |
| source_window.get(), drag_start_location); |
| delegate.DragUpdate(drag_start_location); |
| delegate.DragUpdate(drag_end_location); |
| |
| // |source_window| should remain snapped and it's bounds should not change. |
| EXPECT_EQ(source_window.get(), |
| split_view_controller->GetSnappedWindow(snap_position)); |
| EXPECT_EQ(original_bounds, source_window->bounds()); |
| } |
| |
| // Everything should still be the same after the drag ends. |
| EXPECT_EQ(source_window.get(), |
| split_view_controller->GetSnappedWindow(snap_position)); |
| EXPECT_EQ(original_bounds, source_window->bounds()); |
| } |
| |
| // Make sure metrics is recorded during tab dragging in tablet mode with |
| // webui tab strip enable. |
| TEST_F(TabDragDropDelegateTest, TabDraggingHistogram) { |
| base::HistogramTester histogram_tester; |
| |
| std::unique_ptr<aura::Window> source_window = CreateToplevelTestWindow(); |
| EXPECT_FALSE( |
| SplitViewController::Get(source_window.get())->InTabletSplitViewMode()); |
| |
| const gfx::Point drag_start_location = source_window->bounds().CenterPoint(); |
| |
| // Emulate a drag session ending in a drop to a new window. This should |
| // generate a histogram. |
| auto delegate = std::make_unique<TabDragDropDelegate>( |
| Shell::GetPrimaryRootWindow(), source_window.get(), drag_start_location); |
| delegate->DragUpdate(drag_start_location + gfx::Vector2d(1, 0)); |
| EXPECT_TRUE(ui::WaitForNextFrameToBePresented( |
| source_window->layer()->GetCompositor())); |
| |
| // Check that a new window is requested. Assume the correct drop data |
| // is passed. Return the new window. |
| std::unique_ptr<aura::Window> new_window = CreateToplevelTestWindow(); |
| EXPECT_CALL(*mock_new_window_delegate(), |
| NewWindowForDetachingTab(source_window.get(), _, _)) |
| .Times(1) |
| .WillOnce(RunOnceCallback<2>(new_window.get())); |
| delegate.release()->DropAndDeleteSelf( |
| drag_start_location + gfx::Vector2d(1, 0), ui::OSExchangeData()); |
| EXPECT_TRUE(ui::WaitForNextFrameToBePresented( |
| source_window->layer()->GetCompositor())); |
| |
| EXPECT_FALSE( |
| SplitViewController::Get(source_window.get())->InTabletSplitViewMode()); |
| histogram_tester.ExpectTotalCount("Ash.TabDrag.PresentationTime.TabletMode", |
| 1); |
| histogram_tester.ExpectTotalCount( |
| "Ash.TabDrag.PresentationTime.MaxLatency.TabletMode", 1); |
| } |
| |
| // There are edge cases where a dragging tab closes itself before being dropped. |
| // In these cases new window will be nullptr and it |
| // should be handled gracefully. https://crbug.com/1286203 |
| TEST_F(TabDragDropDelegateTest, DropWithoutNewWindow) { |
| std::unique_ptr<aura::Window> source_window = CreateToplevelTestWindow(); |
| const gfx::Point drag_location = source_window->bounds().CenterPoint(); |
| auto delegate = std::make_unique<TabDragDropDelegate>( |
| Shell::GetPrimaryRootWindow(), source_window.get(), drag_location); |
| delegate->OnNewBrowserWindowCreated(drag_location, /*new_window=*/nullptr); |
| } |
| |
| // Tests that if tab dragging is started on a floated window and then canceled, |
| // the float window returns to its original bounds. |
| TEST_F(TabDragDropDelegateTest, CancelTabDragWithFloatedWindow) { |
| // Create a floated window. |
| std::unique_ptr<aura::Window> source_window = CreateToplevelTestWindow(); |
| source_window->SetProperty(aura::client::kAppType, |
| static_cast<int>(AppType::BROWSER)); |
| wm::ActivateWindow(source_window.get()); |
| PressAndReleaseKey(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN); |
| ASSERT_TRUE(WindowState::Get(source_window.get())->IsFloated()); |
| const gfx::Rect original_bounds = source_window->GetBoundsInScreen(); |
| |
| // Simulate tab dragging from the floated source window. |
| auto delegate = std::make_unique<TabDragDropDelegate>( |
| Shell::GetPrimaryRootWindow(), source_window.get(), |
| source_window->bounds().CenterPoint()); |
| delegate.reset(); |
| EXPECT_EQ(original_bounds, source_window->GetBoundsInScreen()); |
| } |
| |
| TEST_F(TabDragDropDelegateTest, CaptureShouldBeReleasedAfterDrop) { |
| std::unique_ptr<aura::Window> source_window = |
| CreateToplevelTestWindow(gfx::Rect(0, 0, 10, 10)); |
| |
| constexpr gfx::Point kDragStartLocation(5, 5); |
| |
| // Emulate a drag session ending in a drop to a new window. |
| auto* delegate = new TabDragDropDelegate( |
| Shell::GetPrimaryRootWindow(), source_window.get(), kDragStartLocation); |
| |
| delegate->TakeCapture(Shell::GetPrimaryRootWindow(), source_window.get(), |
| base::BindLambdaForTesting([]() {}), |
| ui::TransferTouchesBehavior::kCancel); |
| |
| delegate->DragUpdate(kDragStartLocation); |
| delegate->DragUpdate(kDragStartLocation + gfx::Vector2d(10, 0)); |
| |
| // Input capture should still be active. |
| EXPECT_TRUE(ash::window_util::GetCaptureWindow()); |
| |
| NewWindowDelegate::NewWindowForDetachingTabCallback new_window_callback; |
| EXPECT_CALL(*mock_new_window_delegate(), |
| NewWindowForDetachingTab(source_window.get(), _, _)) |
| .Times(1) |
| .WillOnce( |
| [&](aura::Window* source_window, const ui::OSExchangeData& drop_data, |
| NewWindowDelegate::NewWindowForDetachingTabCallback callback) { |
| new_window_callback = std::move(callback); |
| }); |
| |
| delegate->DropAndDeleteSelf(kDragStartLocation + gfx::Vector2d(10, 0), |
| ui::OSExchangeData()); |
| |
| // Input capture should have been released. |
| EXPECT_FALSE(ash::window_util::GetCaptureWindow()); |
| |
| std::move(new_window_callback).Run(source_window.get()); |
| } |
| |
| } // namespace ash |