tab_drag_drop_delegate_unittest.cc 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581
  1. // Copyright 2020 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "ash/drag_drop/tab_drag_drop_delegate.h"
  5. #include <memory>
  6. #include <string>
  7. #include <utility>
  8. #include <vector>
  9. #include "ash/constants/ash_features.h"
  10. #include "ash/public/cpp/test/test_new_window_delegate.h"
  11. #include "ash/scoped_animation_disabler.h"
  12. #include "ash/screen_util.h"
  13. #include "ash/shell.h"
  14. #include "ash/test/ash_test_base.h"
  15. #include "ash/test_shell_delegate.h"
  16. #include "ash/wm/overview/overview_controller.h"
  17. #include "ash/wm/splitview/split_view_controller.h"
  18. #include "ash/wm/tablet_mode/tablet_mode_controller_test_api.h"
  19. #include "base/containers/flat_map.h"
  20. #include "base/pickle.h"
  21. #include "base/strings/utf_string_conversions.h"
  22. #include "base/test/gmock_callback_support.h"
  23. #include "base/test/metrics/histogram_tester.h"
  24. #include "base/test/scoped_feature_list.h"
  25. #include "testing/gmock/include/gmock/gmock.h"
  26. #include "ui/base/clipboard/clipboard_format_type.h"
  27. #include "ui/base/clipboard/custom_data_helper.h"
  28. #include "ui/base/dragdrop/os_exchange_data.h"
  29. #include "ui/compositor/layer.h"
  30. #include "ui/compositor/test/test_utils.h"
  31. #include "ui/events/test/event_generator.h"
  32. #include "ui/gfx/geometry/vector2d.h"
  33. using ::base::test::RunOnceCallback;
  34. using ::testing::_;
  35. using ::testing::NiceMock;
  36. using ::testing::Return;
  37. namespace ash {
  38. namespace {
  39. constexpr int kWebUITabStripHeight = 100;
  40. class MockShellDelegate : public TestShellDelegate {
  41. public:
  42. MockShellDelegate() = default;
  43. ~MockShellDelegate() override = default;
  44. MOCK_METHOD(bool, IsTabDrag, (const ui::OSExchangeData&), (override));
  45. int GetBrowserWebUITabStripHeight() override { return kWebUITabStripHeight; }
  46. };
  47. class MockNewWindowDelegate : public TestNewWindowDelegate {
  48. public:
  49. MockNewWindowDelegate() = default;
  50. ~MockNewWindowDelegate() override = default;
  51. MOCK_METHOD(void,
  52. NewWindowForDetachingTab,
  53. (aura::Window*,
  54. const ui::OSExchangeData&,
  55. NewWindowForDetachingTabCallback),
  56. (override));
  57. };
  58. } // namespace
  59. class TabDragDropDelegateTest : public AshTestBase {
  60. public:
  61. TabDragDropDelegateTest() {
  62. ash::features::SetWebUITabStripEnabled(true);
  63. scoped_feature_list_.InitAndEnableFeature(
  64. ash::features::kWebUITabStripTabDragIntegration);
  65. }
  66. // AshTestBase:
  67. void SetUp() override {
  68. auto mock_new_window_delegate =
  69. std::make_unique<NiceMock<MockNewWindowDelegate>>();
  70. mock_new_window_delegate_ptr_ = mock_new_window_delegate.get();
  71. test_new_window_delegate_provider_ =
  72. std::make_unique<TestNewWindowDelegateProvider>(
  73. std::move(mock_new_window_delegate));
  74. auto mock_shell_delegate = std::make_unique<NiceMock<MockShellDelegate>>();
  75. mock_shell_delegate_ = mock_shell_delegate.get();
  76. AshTestBase::SetUp(std::move(mock_shell_delegate));
  77. ash::TabletModeControllerTestApi().EnterTabletMode();
  78. // Create a dummy window and exit overview mode since drags can't be
  79. // initiated from overview mode.
  80. dummy_window_ = CreateToplevelTestWindow();
  81. ASSERT_TRUE(ExitOverview());
  82. }
  83. void TearDown() override {
  84. // Must be deleted before AshTestBase's tear down.
  85. dummy_window_.reset();
  86. // Clear our pointer before the object is destroyed.
  87. mock_shell_delegate_ = nullptr;
  88. test_new_window_delegate_provider_.reset();
  89. AshTestBase::TearDown();
  90. }
  91. MockShellDelegate* mock_shell_delegate() { return mock_shell_delegate_; }
  92. MockNewWindowDelegate* mock_new_window_delegate() {
  93. return static_cast<MockNewWindowDelegate*>(
  94. NewWindowDelegate::GetInstance());
  95. }
  96. private:
  97. base::test::ScopedFeatureList scoped_feature_list_;
  98. NiceMock<MockShellDelegate>* mock_shell_delegate_ = nullptr;
  99. std::unique_ptr<TestNewWindowDelegateProvider>
  100. test_new_window_delegate_provider_;
  101. NiceMock<MockNewWindowDelegate>* mock_new_window_delegate_ptr_ = nullptr;
  102. std::unique_ptr<aura::Window> dummy_window_;
  103. };
  104. TEST_F(TabDragDropDelegateTest, ForwardsDragCheckToShellDelegate) {
  105. ON_CALL(*mock_shell_delegate(), IsTabDrag(_)).WillByDefault(Return(false));
  106. EXPECT_FALSE(TabDragDropDelegate::IsChromeTabDrag(ui::OSExchangeData()));
  107. ON_CALL(*mock_shell_delegate(), IsTabDrag(_)).WillByDefault(Return(true));
  108. EXPECT_TRUE(TabDragDropDelegate::IsChromeTabDrag(ui::OSExchangeData()));
  109. }
  110. TEST_F(TabDragDropDelegateTest, DragToExistingTabStrip) {
  111. // Create a fake source window. Its details don't matter.
  112. std::unique_ptr<aura::Window> source_window = CreateToplevelTestWindow();
  113. // A new window shouldn't be created in this case.
  114. EXPECT_CALL(*mock_new_window_delegate(), NewWindowForDetachingTab(_, _, _))
  115. .Times(0);
  116. // Emulate a drag session whose drop target accepts the drop. In this
  117. // case, TabDragDropDelegate::Drop() is not called.
  118. TabDragDropDelegate delegate(Shell::GetPrimaryRootWindow(),
  119. source_window.get(), gfx::Point(0, 0));
  120. delegate.DragUpdate(gfx::Point(1, 0));
  121. delegate.DragUpdate(gfx::Point(2, 0));
  122. // Let |delegate| be destroyed without a Drop() call.
  123. }
  124. TEST_F(TabDragDropDelegateTest, DragToNewWindow) {
  125. // Create the source window. This should automatically fill the work area
  126. // since we're in tablet mode.
  127. std::unique_ptr<aura::Window> source_window = CreateToplevelTestWindow();
  128. EXPECT_FALSE(
  129. SplitViewController::Get(source_window.get())->InTabletSplitViewMode());
  130. const gfx::Point drag_start_location = source_window->bounds().CenterPoint();
  131. // Emulate a drag session ending in a drop to a new window.
  132. auto delegate = std::make_unique<TabDragDropDelegate>(
  133. Shell::GetPrimaryRootWindow(), source_window.get(), drag_start_location);
  134. delegate->DragUpdate(drag_start_location);
  135. delegate->DragUpdate(drag_start_location + gfx::Vector2d(1, 0));
  136. delegate->DragUpdate(drag_start_location + gfx::Vector2d(2, 0));
  137. // Check that a new window is requested. Assume the correct drop data
  138. // is passed. Return the new window.
  139. std::unique_ptr<aura::Window> new_window = CreateToplevelTestWindow();
  140. EXPECT_CALL(*mock_new_window_delegate(),
  141. NewWindowForDetachingTab(source_window.get(), _, _))
  142. .Times(1)
  143. .WillOnce(RunOnceCallback<2>(new_window.get()));
  144. delegate.release()->DropAndDeleteSelf(
  145. drag_start_location + gfx::Vector2d(2, 0), ui::OSExchangeData());
  146. EXPECT_FALSE(
  147. SplitViewController::Get(source_window.get())->InTabletSplitViewMode());
  148. }
  149. // When a tab is dragged to the left/right side of the Web Contents. It should
  150. // enter split view.
  151. TEST_F(TabDragDropDelegateTest, DropOnEdgeEntersSplitView) {
  152. // Create the source window. This should automatically fill the work area
  153. // since we're in tablet mode.
  154. std::unique_ptr<aura::Window> source_window = CreateToplevelTestWindow();
  155. // We want to avoid entering overview mode between the delegate.Drop()
  156. // call and |new_window|'s destruction. So we define it here before
  157. // creating it.
  158. std::unique_ptr<aura::Window> new_window;
  159. // Emulate a drag to the right edge of the screen.
  160. const gfx::Point drag_start_location = source_window->bounds().CenterPoint();
  161. const gfx::Point drag_end_location =
  162. screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer(
  163. source_window.get())
  164. .right_center();
  165. auto delegate = std::make_unique<TabDragDropDelegate>(
  166. Shell::GetPrimaryRootWindow(), source_window.get(), drag_start_location);
  167. delegate->DragUpdate(drag_start_location);
  168. delegate->DragUpdate(drag_end_location);
  169. new_window = CreateToplevelTestWindow();
  170. EXPECT_CALL(*mock_new_window_delegate(),
  171. NewWindowForDetachingTab(source_window.get(), _, _))
  172. .Times(1)
  173. .WillOnce(RunOnceCallback<2>(new_window.get()));
  174. delegate.release()->DropAndDeleteSelf(drag_end_location,
  175. ui::OSExchangeData());
  176. SplitViewController* const split_view_controller =
  177. SplitViewController::Get(source_window.get());
  178. EXPECT_TRUE(split_view_controller->InTabletSplitViewMode());
  179. EXPECT_EQ(new_window.get(), split_view_controller->GetSnappedWindow(
  180. SplitViewController::SnapPosition::RIGHT));
  181. EXPECT_EQ(source_window.get(), split_view_controller->GetSnappedWindow(
  182. SplitViewController::SnapPosition::LEFT));
  183. }
  184. // When a tab is dragged to the left/right edge of the tab strip. It should not
  185. // enter split view.
  186. // https://crbug.com/1316070
  187. TEST_F(TabDragDropDelegateTest, DropOnEdgeShouldNotEnterSplitView) {
  188. // Create the source window. This should automatically fill the work area
  189. // since we're in tablet mode.
  190. std::unique_ptr<aura::Window> source_window = CreateToplevelTestWindow();
  191. // We want to avoid entering overview mode between the delegate.Drop()
  192. // call and |new_window|'s destruction. So we define it here before
  193. // creating it.
  194. std::unique_ptr<aura::Window> new_window;
  195. // Emulate a drag to the right edge of the tab strip. It should not enter
  196. // split view.
  197. const gfx::Point drag_start_location = source_window->bounds().CenterPoint();
  198. const gfx::Point drag_end_location =
  199. gfx::Point(source_window->bounds().right(), kWebUITabStripHeight * 0.5);
  200. auto delegate = std::make_unique<TabDragDropDelegate>(
  201. Shell::GetPrimaryRootWindow(), source_window.get(), drag_start_location);
  202. delegate->DragUpdate(drag_start_location);
  203. delegate->DragUpdate(drag_end_location);
  204. new_window = CreateToplevelTestWindow();
  205. EXPECT_CALL(*mock_new_window_delegate(),
  206. NewWindowForDetachingTab(source_window.get(), _, _))
  207. .Times(1)
  208. .WillOnce(RunOnceCallback<2>(new_window.get()));
  209. delegate.release()->DropAndDeleteSelf(drag_end_location,
  210. ui::OSExchangeData());
  211. SplitViewController* const split_view_controller =
  212. SplitViewController::Get(source_window.get());
  213. EXPECT_FALSE(split_view_controller->InTabletSplitViewMode());
  214. }
  215. TEST_F(TabDragDropDelegateTest, DropTabInSplitViewMode) {
  216. // Enter tablet split view mode by snap the source window to the left.
  217. std::unique_ptr<aura::Window> source_window = CreateToplevelTestWindow();
  218. SplitViewController* const split_view_controller =
  219. SplitViewController::Get(source_window.get());
  220. split_view_controller->SnapWindow(source_window.get(),
  221. SplitViewController::SnapPosition::LEFT);
  222. EXPECT_TRUE(split_view_controller->InTabletSplitViewMode());
  223. // Snap another window to the right to make sure right split screen is not in
  224. // overview mode.
  225. std::unique_ptr<aura::Window> right_window = CreateToplevelTestWindow();
  226. split_view_controller->SnapWindow(right_window.get(),
  227. SplitViewController::SnapPosition::RIGHT);
  228. const gfx::Point drag_start_location = source_window->bounds().CenterPoint();
  229. auto area =
  230. screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer(
  231. source_window.get());
  232. // Emulate a drag to the right side of the screen.
  233. // |new_window1| should snap to the right split view.
  234. gfx::Point drag_end_location_right(area.width() * 0.8, area.height() * 0.5);
  235. auto delegate1 = std::make_unique<TabDragDropDelegate>(
  236. Shell::GetPrimaryRootWindow(), source_window.get(), drag_start_location);
  237. delegate1->DragUpdate(drag_start_location);
  238. delegate1->DragUpdate(drag_end_location_right);
  239. std::unique_ptr<aura::Window> new_window1 = CreateToplevelTestWindow();
  240. EXPECT_CALL(*mock_new_window_delegate(),
  241. NewWindowForDetachingTab(source_window.get(), _, _))
  242. .Times(1)
  243. .WillOnce(RunOnceCallback<2>(new_window1.get()));
  244. delegate1.release()->DropAndDeleteSelf(drag_end_location_right,
  245. ui::OSExchangeData());
  246. EXPECT_TRUE(split_view_controller->InTabletSplitViewMode());
  247. EXPECT_EQ(new_window1.get(), split_view_controller->GetSnappedWindow(
  248. SplitViewController::SnapPosition::RIGHT));
  249. EXPECT_EQ(source_window.get(), split_view_controller->GetSnappedWindow(
  250. SplitViewController::SnapPosition::LEFT));
  251. new_window1.reset(); // Close |new_window1|.
  252. // Emulate a drag to the left side of the screen.
  253. // |new_window2| should snap to the left split view.
  254. // |source_window| should go into overview mode.
  255. gfx::Point drag_end_location_left(area.width() * 0.2, area.height() * 0.5);
  256. auto delegate2 = std::make_unique<TabDragDropDelegate>(
  257. Shell::GetPrimaryRootWindow(), source_window.get(), drag_start_location);
  258. delegate2->DragUpdate(drag_start_location);
  259. delegate2->DragUpdate(drag_end_location_left);
  260. std::unique_ptr<aura::Window> new_window2 = CreateToplevelTestWindow();
  261. EXPECT_CALL(*mock_new_window_delegate(),
  262. NewWindowForDetachingTab(source_window.get(), _, _))
  263. .Times(1)
  264. .WillOnce(RunOnceCallback<2>(new_window2.get()));
  265. delegate2.release()->DropAndDeleteSelf(drag_end_location_left,
  266. ui::OSExchangeData());
  267. EXPECT_TRUE(split_view_controller->InTabletSplitViewMode());
  268. EXPECT_EQ(nullptr, split_view_controller->GetSnappedWindow(
  269. SplitViewController::SnapPosition::RIGHT));
  270. EXPECT_EQ(new_window2.get(), split_view_controller->GetSnappedWindow(
  271. SplitViewController::SnapPosition::LEFT));
  272. ASSERT_TRUE(Shell::Get()->overview_controller()->InOverviewSession());
  273. auto windows_list = Shell::Get()
  274. ->overview_controller()
  275. ->GetWindowsListInOverviewGridsForTest();
  276. EXPECT_NE(
  277. std::end(windows_list),
  278. std::find(windows_list.begin(), windows_list.end(), source_window.get()));
  279. }
  280. TEST_F(TabDragDropDelegateTest, DropTabToOverviewMode) {
  281. // Enter tablet split view mode by snap the source window to the left.
  282. std::unique_ptr<aura::Window> source_window = CreateToplevelTestWindow();
  283. SplitViewController* const split_view_controller =
  284. SplitViewController::Get(source_window.get());
  285. split_view_controller->SnapWindow(source_window.get(),
  286. SplitViewController::SnapPosition::LEFT);
  287. EXPECT_TRUE(split_view_controller->InTabletSplitViewMode());
  288. ASSERT_TRUE(Shell::Get()->overview_controller()->InOverviewSession());
  289. const gfx::Point drag_start_location = source_window->bounds().CenterPoint();
  290. auto area =
  291. screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer(
  292. source_window.get());
  293. // Emulate a drag to the right side of the screen.
  294. // |new_window1| should snap to overview mode.
  295. gfx::Point drag_end_location_right(area.width() * 0.8, area.height() * 0.5);
  296. auto delegate1 = std::make_unique<TabDragDropDelegate>(
  297. Shell::GetPrimaryRootWindow(), source_window.get(), drag_start_location);
  298. delegate1->DragUpdate(drag_start_location);
  299. delegate1->DragUpdate(drag_end_location_right);
  300. std::unique_ptr<aura::Window> new_window = CreateToplevelTestWindow();
  301. EXPECT_CALL(*mock_new_window_delegate(),
  302. NewWindowForDetachingTab(source_window.get(), _, _))
  303. .Times(1)
  304. .WillOnce(RunOnceCallback<2>(new_window.get()));
  305. delegate1.release()->DropAndDeleteSelf(drag_end_location_right,
  306. ui::OSExchangeData());
  307. EXPECT_EQ(nullptr, split_view_controller->GetSnappedWindow(
  308. SplitViewController::SnapPosition::RIGHT));
  309. auto windows_list = Shell::Get()
  310. ->overview_controller()
  311. ->GetWindowsListInOverviewGridsForTest();
  312. EXPECT_NE(
  313. std::end(windows_list),
  314. std::find(windows_list.begin(), windows_list.end(), new_window.get()));
  315. }
  316. TEST_F(TabDragDropDelegateTest, WillNotDropTabToOverviewModeInSnappingZone) {
  317. // Enter tablet split view mode by snap the source window to the left.
  318. std::unique_ptr<aura::Window> source_window = CreateToplevelTestWindow();
  319. SplitViewController* const split_view_controller =
  320. SplitViewController::Get(source_window.get());
  321. split_view_controller->SnapWindow(source_window.get(),
  322. SplitViewController::SnapPosition::LEFT);
  323. EXPECT_TRUE(split_view_controller->InTabletSplitViewMode());
  324. ASSERT_TRUE(Shell::Get()->overview_controller()->InOverviewSession());
  325. const gfx::Point drag_start_location = source_window->bounds().CenterPoint();
  326. auto area =
  327. screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer(
  328. source_window.get());
  329. // Emulate a drag to the right snapping zone of the screen.
  330. // |new_window1| should not snap to overview mode.
  331. gfx::Point drag_end_location_right(area.width() * 0.95, area.height() * 0.5);
  332. auto delegate1 = std::make_unique<TabDragDropDelegate>(
  333. Shell::GetPrimaryRootWindow(), source_window.get(), drag_start_location);
  334. delegate1->DragUpdate(drag_start_location);
  335. delegate1->DragUpdate(drag_end_location_right);
  336. std::unique_ptr<aura::Window> new_window = CreateToplevelTestWindow();
  337. EXPECT_CALL(*mock_new_window_delegate(),
  338. NewWindowForDetachingTab(source_window.get(), _, _))
  339. .Times(1)
  340. .WillOnce(RunOnceCallback<2>(new_window.get()));
  341. delegate1.release()->DropAndDeleteSelf(drag_end_location_right,
  342. ui::OSExchangeData());
  343. EXPECT_EQ(new_window.get(), split_view_controller->GetSnappedWindow(
  344. SplitViewController::SnapPosition::RIGHT));
  345. ASSERT_FALSE(Shell::Get()->overview_controller()->InOverviewSession());
  346. }
  347. TEST_F(TabDragDropDelegateTest, WillNotDropTabToOverviewMode) {
  348. // Enter tablet split view mode by snap the source window to the left.
  349. std::unique_ptr<aura::Window> source_window = CreateToplevelTestWindow();
  350. SplitViewController* const split_view_controller =
  351. SplitViewController::Get(source_window.get());
  352. split_view_controller->SnapWindow(source_window.get(),
  353. SplitViewController::SnapPosition::LEFT);
  354. EXPECT_TRUE(split_view_controller->InTabletSplitViewMode());
  355. ASSERT_TRUE(Shell::Get()->overview_controller()->InOverviewSession());
  356. const gfx::Point drag_start_location = source_window->bounds().CenterPoint();
  357. auto area =
  358. screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer(
  359. source_window.get());
  360. // Emulate a drag to the left side of the screen.
  361. // |new_window1| should not snap to overview mode.
  362. gfx::Point drag_end_location_right(area.width() * 0.2, area.height() * 0.5);
  363. auto delegate1 = std::make_unique<TabDragDropDelegate>(
  364. Shell::GetPrimaryRootWindow(), source_window.get(), drag_start_location);
  365. delegate1->DragUpdate(drag_start_location);
  366. delegate1->DragUpdate(drag_end_location_right);
  367. std::unique_ptr<aura::Window> new_window = CreateToplevelTestWindow();
  368. EXPECT_CALL(*mock_new_window_delegate(),
  369. NewWindowForDetachingTab(source_window.get(), _, _))
  370. .Times(1)
  371. .WillOnce(RunOnceCallback<2>(new_window.get()));
  372. delegate1.release()->DropAndDeleteSelf(drag_end_location_right,
  373. ui::OSExchangeData());
  374. EXPECT_EQ(new_window.get(), split_view_controller->GetSnappedWindow(
  375. SplitViewController::SnapPosition::LEFT));
  376. auto windows_list = Shell::Get()
  377. ->overview_controller()
  378. ->GetWindowsListInOverviewGridsForTest();
  379. EXPECT_EQ(
  380. std::end(windows_list),
  381. std::find(windows_list.begin(), windows_list.end(), new_window.get()));
  382. }
  383. TEST_F(TabDragDropDelegateTest, SourceWindowBoundsUpdatedWhileDragging) {
  384. // Create the source window. This should automatically fill the work area
  385. // since we're in tablet mode.
  386. std::unique_ptr<aura::Window> source_window = CreateToplevelTestWindow();
  387. const gfx::Rect original_bounds = source_window->bounds();
  388. // Drag a few pixels away to trigger window scaling, then to the
  389. // screen edge to visually snap the source window.
  390. const gfx::Point drag_start_location = source_window->bounds().CenterPoint();
  391. const gfx::Point drag_mid_location =
  392. drag_start_location + gfx::Vector2d(10, 0);
  393. const gfx::Point drag_end_location =
  394. screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer(
  395. source_window.get())
  396. .left_center();
  397. {
  398. TabDragDropDelegate delegate(Shell::GetPrimaryRootWindow(),
  399. source_window.get(), drag_start_location);
  400. delegate.DragUpdate(drag_start_location);
  401. delegate.DragUpdate(drag_mid_location);
  402. // |source_window| should be shrunk in all directions
  403. EXPECT_GT(source_window->bounds().x(), original_bounds.x());
  404. EXPECT_GT(source_window->bounds().y(), original_bounds.y());
  405. EXPECT_LT(source_window->bounds().right(), original_bounds.right());
  406. EXPECT_LT(source_window->bounds().bottom(), original_bounds.bottom());
  407. delegate.DragUpdate(drag_end_location);
  408. // |source_window| should appear in the snapped position, but not
  409. // actually be snapped.
  410. SplitViewController* const split_view_controller =
  411. SplitViewController::Get(source_window.get());
  412. EXPECT_EQ(
  413. source_window->bounds(),
  414. split_view_controller->GetSnappedWindowBoundsInParent(
  415. SplitViewController::SnapPosition::RIGHT, source_window.get()));
  416. EXPECT_FALSE(split_view_controller->InSplitViewMode());
  417. }
  418. // The original bounds should be restored.
  419. EXPECT_EQ(source_window->bounds(), original_bounds);
  420. }
  421. TEST_F(TabDragDropDelegateTest, SnappedSourceWindowNotMoved) {
  422. // Create the source window. This should automatically fill the work area
  423. // since we're in tablet mode.
  424. std::unique_ptr<aura::Window> source_window = CreateToplevelTestWindow();
  425. SplitViewController* const split_view_controller =
  426. SplitViewController::Get(source_window.get());
  427. SplitViewController::SnapPosition const snap_position =
  428. SplitViewController::SnapPosition::LEFT;
  429. split_view_controller->SnapWindow(source_window.get(), snap_position);
  430. const gfx::Rect original_bounds = source_window->bounds();
  431. const gfx::Point drag_start_location = source_window->bounds().CenterPoint();
  432. const gfx::Point drag_end_location =
  433. drag_start_location + gfx::Vector2d(10, 0);
  434. {
  435. TabDragDropDelegate delegate(Shell::GetPrimaryRootWindow(),
  436. source_window.get(), drag_start_location);
  437. delegate.DragUpdate(drag_start_location);
  438. delegate.DragUpdate(drag_end_location);
  439. // |source_window| should remain snapped and it's bounds should not change.
  440. EXPECT_EQ(source_window.get(),
  441. split_view_controller->GetSnappedWindow(snap_position));
  442. EXPECT_EQ(original_bounds, source_window->bounds());
  443. }
  444. // Everything should still be the same after the drag ends.
  445. EXPECT_EQ(source_window.get(),
  446. split_view_controller->GetSnappedWindow(snap_position));
  447. EXPECT_EQ(original_bounds, source_window->bounds());
  448. }
  449. // Make sure metrics is recorded during tab dragging in tablet mode with
  450. // webui tab strip enable.
  451. TEST_F(TabDragDropDelegateTest, TabDraggingHistogram) {
  452. base::HistogramTester histogram_tester;
  453. std::unique_ptr<aura::Window> source_window = CreateToplevelTestWindow();
  454. EXPECT_FALSE(
  455. SplitViewController::Get(source_window.get())->InTabletSplitViewMode());
  456. const gfx::Point drag_start_location = source_window->bounds().CenterPoint();
  457. // Emulate a drag session ending in a drop to a new window. This should
  458. // generate a histogram.
  459. auto delegate = std::make_unique<TabDragDropDelegate>(
  460. Shell::GetPrimaryRootWindow(), source_window.get(), drag_start_location);
  461. delegate->DragUpdate(drag_start_location + gfx::Vector2d(1, 0));
  462. EXPECT_TRUE(ui::WaitForNextFrameToBePresented(
  463. source_window->layer()->GetCompositor()));
  464. // Check that a new window is requested. Assume the correct drop data
  465. // is passed. Return the new window.
  466. std::unique_ptr<aura::Window> new_window = CreateToplevelTestWindow();
  467. EXPECT_CALL(*mock_new_window_delegate(),
  468. NewWindowForDetachingTab(source_window.get(), _, _))
  469. .Times(1)
  470. .WillOnce(RunOnceCallback<2>(new_window.get()));
  471. delegate.release()->DropAndDeleteSelf(
  472. drag_start_location + gfx::Vector2d(1, 0), ui::OSExchangeData());
  473. EXPECT_TRUE(ui::WaitForNextFrameToBePresented(
  474. source_window->layer()->GetCompositor()));
  475. EXPECT_FALSE(
  476. SplitViewController::Get(source_window.get())->InTabletSplitViewMode());
  477. histogram_tester.ExpectTotalCount("Ash.TabDrag.PresentationTime.TabletMode",
  478. 1);
  479. histogram_tester.ExpectTotalCount(
  480. "Ash.TabDrag.PresentationTime.MaxLatency.TabletMode", 1);
  481. }
  482. // There are edge cases where a dragging tab closes itself before being dropped.
  483. // In these cases new window will be nullptr and it
  484. // should be handled gracefully. https://crbug.com/1286203
  485. TEST_F(TabDragDropDelegateTest, DropWithoutNewWindow) {
  486. std::unique_ptr<aura::Window> source_window = CreateToplevelTestWindow();
  487. const gfx::Point drag_location = source_window->bounds().CenterPoint();
  488. auto delegate = std::make_unique<TabDragDropDelegate>(
  489. Shell::GetPrimaryRootWindow(), source_window.get(), drag_location);
  490. delegate->OnNewBrowserWindowCreated(drag_location, /*new_window=*/nullptr);
  491. }
  492. } // namespace ash