tab_drag_drop_delegate.cc 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  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 "ash/constants/app_types.h"
  6. #include "ash/constants/ash_features.h"
  7. #include "ash/public/cpp/new_window_delegate.h"
  8. #include "ash/public/cpp/window_properties.h"
  9. #include "ash/screen_util.h"
  10. #include "ash/shell.h"
  11. #include "ash/shell_delegate.h"
  12. #include "ash/wm/overview/overview_controller.h"
  13. #include "ash/wm/overview/overview_session.h"
  14. #include "ash/wm/splitview/split_view_constants.h"
  15. #include "ash/wm/splitview/split_view_controller.h"
  16. #include "ash/wm/splitview/split_view_drag_indicators.h"
  17. #include "ash/wm/splitview/split_view_utils.h"
  18. #include "ash/wm/tablet_mode/tablet_mode_browser_window_drag_session_windows_hider.h"
  19. #include "ash/wm/wm_metrics.h"
  20. #include "base/pickle.h"
  21. #include "base/strings/utf_string_conversions.h"
  22. #include "chromeos/crosapi/cpp/lacros_startup_state.h"
  23. #include "ui/aura/client/aura_constants.h"
  24. #include "ui/base/clipboard/clipboard_format_type.h"
  25. #include "ui/base/clipboard/custom_data_helper.h"
  26. #include "ui/base/dragdrop/os_exchange_data.h"
  27. #include "ui/compositor/layer.h"
  28. #include "ui/compositor/layer_animator.h"
  29. #include "ui/compositor/presentation_time_recorder.h"
  30. #include "ui/compositor/scoped_layer_animation_settings.h"
  31. #include "ui/gfx/geometry/rect.h"
  32. #include "ui/wm/core/coordinate_conversion.h"
  33. namespace ash {
  34. namespace {
  35. // The following distances are copied from tablet_mode_window_drag_delegate.cc.
  36. // TODO(https://crbug.com/1069869): share these constants.
  37. // Items dragged to within |kDistanceFromEdgeDp| of the screen will get snapped
  38. // even if they have not moved by |kMinimumDragToSnapDistanceDp|.
  39. constexpr float kDistanceFromEdgeDp = 16.f;
  40. // The minimum distance that an item must be moved before it is snapped. This
  41. // prevents accidental snaps.
  42. constexpr float kMinimumDragToSnapDistanceDp = 96.f;
  43. // The scale factor that the source window should scale if the source window is
  44. // not the dragged window && is not in splitscreen when drag starts && the user
  45. // has dragged the window to pass the |kIndicatorThresholdRatio| vertical
  46. // threshold.
  47. constexpr float kSourceWindowScale = 0.85;
  48. // The UMA histogram that records presentation time for tab dragging in
  49. // tablet mode with webui tab strip enable.
  50. constexpr char kTabDraggingInTabletModeHistogram[] =
  51. "Ash.TabDrag.PresentationTime.TabletMode";
  52. constexpr char kTabDraggingInTabletModeMaxLatencyHistogram[] =
  53. "Ash.TabDrag.PresentationTime.MaxLatency.TabletMode";
  54. DEFINE_UI_CLASS_PROPERTY_KEY(bool, kIsSourceWindowForDrag, false)
  55. bool IsLacrosWindow(const aura::Window* window) {
  56. auto app_type =
  57. static_cast<AppType>(window->GetProperty(aura::client::kAppType));
  58. return app_type == AppType::LACROS;
  59. }
  60. // Returns the overview session if overview mode is active, otherwise returns
  61. // nullptr.
  62. OverviewSession* GetOverviewSession() {
  63. return Shell::Get()->overview_controller()->InOverviewSession()
  64. ? Shell::Get()->overview_controller()->overview_session()
  65. : nullptr;
  66. }
  67. } // namespace
  68. // static
  69. bool TabDragDropDelegate::IsChromeTabDrag(const ui::OSExchangeData& drag_data) {
  70. if (!features::IsWebUITabStripTabDragIntegrationEnabled())
  71. return false;
  72. return Shell::Get()->shell_delegate()->IsTabDrag(drag_data);
  73. }
  74. // static
  75. bool TabDragDropDelegate::IsSourceWindowForDrag(const aura::Window* window) {
  76. return window->GetProperty(kIsSourceWindowForDrag);
  77. }
  78. TabDragDropDelegate::TabDragDropDelegate(
  79. aura::Window* root_window,
  80. aura::Window* source_window,
  81. const gfx::Point& start_location_in_screen)
  82. : root_window_(root_window),
  83. source_window_(source_window->GetToplevelWindow()),
  84. start_location_in_screen_(start_location_in_screen) {
  85. DCHECK(root_window_);
  86. DCHECK(source_window_);
  87. source_window_->SetProperty(kIsSourceWindowForDrag, true);
  88. split_view_drag_indicators_ =
  89. std::make_unique<SplitViewDragIndicators>(root_window_);
  90. tab_dragging_recorder_ = CreatePresentationTimeHistogramRecorder(
  91. source_window_->layer()->GetCompositor(),
  92. kTabDraggingInTabletModeHistogram,
  93. kTabDraggingInTabletModeMaxLatencyHistogram);
  94. }
  95. TabDragDropDelegate::~TabDragDropDelegate() {
  96. tab_dragging_recorder_.reset();
  97. if (source_window_->is_destroying())
  98. return;
  99. if (!source_window_->GetProperty(kIsSourceWindowForDrag))
  100. return;
  101. // If we didn't drop to a new window, we must restore the original window.
  102. RestoreSourceWindowBounds();
  103. source_window_->ClearProperty(kIsSourceWindowForDrag);
  104. }
  105. void TabDragDropDelegate::DragUpdate(const gfx::Point& location_in_screen) {
  106. const gfx::Rect area =
  107. screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer(
  108. root_window_);
  109. SplitViewController::SnapPosition snap_position =
  110. ::ash::GetSnapPositionForLocation(
  111. Shell::GetPrimaryRootWindow(), location_in_screen,
  112. start_location_in_screen_,
  113. /*snap_distance_from_edge=*/kDistanceFromEdgeDp,
  114. /*minimum_drag_distance=*/kMinimumDragToSnapDistanceDp,
  115. /*horizontal_edge_inset=*/area.width() *
  116. kHighlightScreenPrimaryAxisRatio +
  117. kHighlightScreenEdgePaddingDp,
  118. /*vertical_edge_inset=*/area.height() *
  119. kHighlightScreenPrimaryAxisRatio +
  120. kHighlightScreenEdgePaddingDp);
  121. if (ShouldPreventSnapToTheEdge(location_in_screen))
  122. snap_position = SplitViewController::SnapPosition::NONE;
  123. split_view_drag_indicators_->SetWindowDraggingState(
  124. SplitViewDragIndicators::ComputeWindowDraggingState(
  125. true, SplitViewDragIndicators::WindowDraggingState::kFromTop,
  126. snap_position));
  127. UpdateSourceWindowBoundsIfNecessary(snap_position, location_in_screen);
  128. tab_dragging_recorder_->RequestNext();
  129. }
  130. void TabDragDropDelegate::DropAndDeleteSelf(
  131. const gfx::Point& location_in_screen,
  132. const ui::OSExchangeData& drop_data) {
  133. tab_dragging_recorder_.reset();
  134. auto closure = base::BindOnce(&TabDragDropDelegate::OnNewBrowserWindowCreated,
  135. base::Owned(this), location_in_screen);
  136. NewWindowDelegate::GetPrimary()->NewWindowForDetachingTab(
  137. source_window_, drop_data, std::move(closure));
  138. }
  139. void TabDragDropDelegate::OnNewBrowserWindowCreated(
  140. const gfx::Point& location_in_screen,
  141. aura::Window* new_window) {
  142. auto is_lacros = IsLacrosWindow(source_window_);
  143. // https://crbug.com/1286203:
  144. // It's possible new window is created when the dragged WebContents
  145. // closes itself during the drag session.
  146. if (!new_window) {
  147. if (is_lacros && !crosapi::lacros_startup_state::IsLacrosPrimaryEnabled()) {
  148. LOG(ERROR)
  149. << "New browser window creation for tab detaching failed.\n"
  150. << "Check whether about:flags#lacros-primary is enabled or "
  151. << "--enable-features=LacrosPrimary is passed in when launching Ash";
  152. }
  153. return;
  154. }
  155. const gfx::Rect area =
  156. screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer(
  157. root_window_);
  158. SplitViewController::SnapPosition snap_position_in_snapping_zone =
  159. ash::GetSnapPosition(
  160. root_window_, new_window, location_in_screen,
  161. start_location_in_screen_,
  162. /*snap_distance_from_edge=*/kDistanceFromEdgeDp,
  163. /*minimum_drag_distance=*/kMinimumDragToSnapDistanceDp,
  164. /*horizontal_edge_inset=*/area.width() *
  165. kHighlightScreenPrimaryAxisRatio +
  166. kHighlightScreenEdgePaddingDp,
  167. /*vertical_edge_inset=*/area.height() *
  168. kHighlightScreenPrimaryAxisRatio +
  169. kHighlightScreenEdgePaddingDp);
  170. if (ShouldPreventSnapToTheEdge(location_in_screen))
  171. snap_position_in_snapping_zone = SplitViewController::SnapPosition::NONE;
  172. if (snap_position_in_snapping_zone == SplitViewController::SnapPosition::NONE)
  173. RestoreSourceWindowBounds();
  174. // This must be done after restoring the source window's bounds since
  175. // otherwise the SetBounds() call may have no effect.
  176. source_window_->ClearProperty(kIsSourceWindowForDrag);
  177. SplitViewController* const split_view_controller =
  178. SplitViewController::Get(new_window);
  179. // If it's already in split view mode, either snap the new window
  180. // to the left or the right depending on the drop location.
  181. const bool in_split_view_mode = split_view_controller->InSplitViewMode();
  182. SplitViewController::SnapPosition snap_position =
  183. snap_position_in_snapping_zone;
  184. if (in_split_view_mode) {
  185. snap_position =
  186. split_view_controller->ComputeSnapPosition(location_in_screen);
  187. }
  188. if (snap_position == SplitViewController::SnapPosition::NONE)
  189. return;
  190. OverviewSession* overview_session = GetOverviewSession();
  191. // If overview session is present on the other side and the new window is
  192. // about to snap to that side but not in the snapping zone then drop the new
  193. // window into overview.
  194. if (overview_session &&
  195. snap_position_in_snapping_zone ==
  196. SplitViewController::SnapPosition::NONE &&
  197. split_view_controller->GetPositionOfSnappedWindow(source_window_) !=
  198. snap_position) {
  199. overview_session->MergeWindowIntoOverviewForWebUITabStrip(new_window);
  200. } else {
  201. WindowState::Get(new_window)
  202. ->set_snap_action_source(WindowSnapActionSource::kDragTabToSnap);
  203. split_view_controller->SnapWindow(new_window, snap_position,
  204. /*activate_window=*/true);
  205. }
  206. // Do not snap the source window if already in split view mode.
  207. if (in_split_view_mode)
  208. return;
  209. // The tab drag source window is the last window the user was
  210. // interacting with. When dropping into split view, it makes the most
  211. // sense to snap this window to the opposite side. Do this.
  212. SplitViewController::SnapPosition opposite_position =
  213. (snap_position == SplitViewController::SnapPosition::LEFT)
  214. ? SplitViewController::SnapPosition::RIGHT
  215. : SplitViewController::SnapPosition::LEFT;
  216. // |source_window_| is itself a child window of the browser since it
  217. // hosts web content (specifically, the tab strip WebUI). Snap its
  218. // toplevel window which is the browser window.
  219. WindowState::Get(new_window)
  220. ->set_snap_action_source(WindowSnapActionSource::kDragTabToSnap);
  221. split_view_controller->SnapWindow(source_window_, opposite_position);
  222. }
  223. bool TabDragDropDelegate::ShouldPreventSnapToTheEdge(
  224. const gfx::Point& location_in_screen) {
  225. SplitViewController* const split_view_controller =
  226. SplitViewController::Get(source_window_);
  227. return !split_view_controller->InSplitViewMode() &&
  228. split_view_controller->IsLayoutHorizontal(source_window_) &&
  229. location_in_screen.y() <
  230. Shell::Get()->shell_delegate()->GetBrowserWebUITabStripHeight();
  231. }
  232. void TabDragDropDelegate::UpdateSourceWindowBoundsIfNecessary(
  233. SplitViewController::SnapPosition candidate_snap_position,
  234. const gfx::Point& location_in_screen) {
  235. SplitViewController* const split_view_controller =
  236. SplitViewController::Get(source_window_);
  237. if (split_view_controller->IsWindowInSplitView(source_window_))
  238. return;
  239. if (!windows_hider_) {
  240. windows_hider_ =
  241. std::make_unique<TabletModeBrowserWindowDragSessionWindowsHider>(
  242. source_window_, nullptr);
  243. }
  244. gfx::Rect new_source_window_bounds;
  245. if (candidate_snap_position == SplitViewController::SnapPosition::NONE) {
  246. const gfx::Rect area =
  247. screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer(
  248. root_window_);
  249. new_source_window_bounds = area;
  250. // Only shrink the window when the tab is dragged out of WebUI tab strip.
  251. if (location_in_screen.y() >
  252. Shell::Get()->shell_delegate()->GetBrowserWebUITabStripHeight()) {
  253. new_source_window_bounds.ClampToCenteredSize(
  254. gfx::Size(area.width() * kSourceWindowScale,
  255. area.height() * kSourceWindowScale));
  256. }
  257. } else {
  258. const SplitViewController::SnapPosition opposite_position =
  259. (candidate_snap_position == SplitViewController::SnapPosition::LEFT)
  260. ? SplitViewController::SnapPosition::RIGHT
  261. : SplitViewController::SnapPosition::LEFT;
  262. new_source_window_bounds =
  263. SplitViewController::Get(source_window_)
  264. ->GetSnappedWindowBoundsInScreen(opposite_position, source_window_);
  265. }
  266. wm::ConvertRectFromScreen(source_window_->parent(),
  267. &new_source_window_bounds);
  268. if (new_source_window_bounds != source_window_->GetTargetBounds()) {
  269. ui::ScopedLayerAnimationSettings settings(
  270. source_window_->layer()->GetAnimator());
  271. settings.SetPreemptionStrategy(
  272. ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
  273. source_window_->SetBounds(new_source_window_bounds);
  274. }
  275. }
  276. void TabDragDropDelegate::RestoreSourceWindowBounds() {
  277. if (SplitViewController::Get(source_window_)
  278. ->IsWindowInSplitView(source_window_))
  279. return;
  280. const gfx::Rect area =
  281. screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer(
  282. root_window_);
  283. source_window_->SetBounds(area);
  284. }
  285. } // namespace ash