[go: nahoru, domu]

blob: ced0184fe6d026e7258969bb95a0f197fa75e07c [file] [log] [blame]
Avi Drissman3a215d1e2022-09-07 19:43:091// Copyright 2020 The Chromium Authors
Collin Bakerc6fae372020-05-01 23:37:472// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "ash/drag_drop/tab_drag_drop_delegate.h"
6
Antonio Gomes519ab0a82022-01-10 21:03:217#include "ash/constants/app_types.h"
Henrique Ferreiro8422acb2021-06-16 13:56:008#include "ash/constants/ash_features.h"
Xiaoqian Daice2f7842023-03-17 20:39:319#include "ash/drag_drop/tab_drag_drop_windows_hider.h"
Antonio Gomes40900a312021-10-07 10:41:0910#include "ash/public/cpp/new_window_delegate.h"
Collin Baker832c7932020-05-19 22:18:4111#include "ash/public/cpp/window_properties.h"
Collin Bakerc6fae372020-05-01 23:37:4712#include "ash/screen_util.h"
13#include "ash/shell.h"
14#include "ash/shell_delegate.h"
Yuheng Huangca5a28d2022-02-11 21:55:1915#include "ash/wm/overview/overview_controller.h"
16#include "ash/wm/overview/overview_session.h"
Collin Bakerc6fae372020-05-01 23:37:4717#include "ash/wm/splitview/split_view_constants.h"
18#include "ash/wm/splitview/split_view_controller.h"
19#include "ash/wm/splitview/split_view_drag_indicators.h"
20#include "ash/wm/splitview/split_view_utils.h"
Sammie Quonba51e0d02023-04-11 19:54:5321#include "ash/wm/tablet_mode/tablet_mode_window_state.h"
Michele Fan3c6da022023-12-09 00:52:4522#include "ash/wm/window_util.h"
Xiaoqian Dai02ea8092022-02-24 23:16:0223#include "ash/wm/wm_metrics.h"
Collin Bakerc6fae372020-05-01 23:37:4724#include "base/pickle.h"
25#include "base/strings/utf_string_conversions.h"
Antonio Gomes519ab0a82022-01-10 21:03:2126#include "chromeos/crosapi/cpp/lacros_startup_state.h"
27#include "ui/aura/client/aura_constants.h"
Collin Bakerc6fae372020-05-01 23:37:4728#include "ui/base/clipboard/clipboard_format_type.h"
29#include "ui/base/clipboard/custom_data_helper.h"
30#include "ui/base/dragdrop/os_exchange_data.h"
Lei Zhang0c043322021-04-27 16:43:0531#include "ui/compositor/layer.h"
Collin Baker832c7932020-05-19 22:18:4132#include "ui/compositor/layer_animator.h"
Yuheng Huanga6b2b622022-02-14 20:50:4233#include "ui/compositor/presentation_time_recorder.h"
Collin Baker832c7932020-05-19 22:18:4134#include "ui/compositor/scoped_layer_animation_settings.h"
Collin Bakerc6fae372020-05-01 23:37:4735#include "ui/gfx/geometry/rect.h"
Collin Baker832c7932020-05-19 22:18:4136#include "ui/wm/core/coordinate_conversion.h"
Collin Bakerc6fae372020-05-01 23:37:4737
38namespace ash {
39
40namespace {
41
42// The following distances are copied from tablet_mode_window_drag_delegate.cc.
43// TODO(https://crbug.com/1069869): share these constants.
44
45// Items dragged to within |kDistanceFromEdgeDp| of the screen will get snapped
46// even if they have not moved by |kMinimumDragToSnapDistanceDp|.
47constexpr float kDistanceFromEdgeDp = 16.f;
48// The minimum distance that an item must be moved before it is snapped. This
49// prevents accidental snaps.
50constexpr float kMinimumDragToSnapDistanceDp = 96.f;
51
Collin Baker832c7932020-05-19 22:18:4152// The scale factor that the source window should scale if the source window is
53// not the dragged window && is not in splitscreen when drag starts && the user
54// has dragged the window to pass the |kIndicatorThresholdRatio| vertical
55// threshold.
56constexpr float kSourceWindowScale = 0.85;
57
Yichen Zhoub81bae012021-04-09 23:18:1058// The UMA histogram that records presentation time for tab dragging in
59// tablet mode with webui tab strip enable.
60constexpr char kTabDraggingInTabletModeHistogram[] =
61 "Ash.TabDrag.PresentationTime.TabletMode";
62
63constexpr char kTabDraggingInTabletModeMaxLatencyHistogram[] =
64 "Ash.TabDrag.PresentationTime.MaxLatency.TabletMode";
65
Collin Baker832c7932020-05-19 22:18:4166DEFINE_UI_CLASS_PROPERTY_KEY(bool, kIsSourceWindowForDrag, false)
67
Antonio Gomes519ab0a82022-01-10 21:03:2168bool IsLacrosWindow(const aura::Window* window) {
69 auto app_type =
70 static_cast<AppType>(window->GetProperty(aura::client::kAppType));
71 return app_type == AppType::LACROS;
72}
73
Yuheng Huangca5a28d2022-02-11 21:55:1974// Returns the overview session if overview mode is active, otherwise returns
75// nullptr.
76OverviewSession* GetOverviewSession() {
77 return Shell::Get()->overview_controller()->InOverviewSession()
78 ? Shell::Get()->overview_controller()->overview_session()
79 : nullptr;
80}
81
Collin Bakerc6fae372020-05-01 23:37:4782} // namespace
83
84// static
85bool TabDragDropDelegate::IsChromeTabDrag(const ui::OSExchangeData& drag_data) {
Collin Bakere5fb2f692020-05-14 01:24:4486 return Shell::Get()->shell_delegate()->IsTabDrag(drag_data);
Collin Bakerc6fae372020-05-01 23:37:4787}
88
Collin Baker832c7932020-05-19 22:18:4189// static
90bool TabDragDropDelegate::IsSourceWindowForDrag(const aura::Window* window) {
91 return window->GetProperty(kIsSourceWindowForDrag);
92}
93
Collin Bakerc6fae372020-05-01 23:37:4794TabDragDropDelegate::TabDragDropDelegate(
95 aura::Window* root_window,
96 aura::Window* source_window,
97 const gfx::Point& start_location_in_screen)
98 : root_window_(root_window),
Collin Baker832c7932020-05-19 22:18:4199 source_window_(source_window->GetToplevelWindow()),
Collin Bakerc6fae372020-05-01 23:37:47100 start_location_in_screen_(start_location_in_screen) {
Collin Baker832c7932020-05-19 22:18:41101 DCHECK(root_window_);
102 DCHECK(source_window_);
Antonio Gomes5ec1a5a2023-01-19 02:55:50103 source_window_->AddObserver(this);
Collin Baker832c7932020-05-19 22:18:41104 source_window_->SetProperty(kIsSourceWindowForDrag, true);
Collin Bakerc6fae372020-05-01 23:37:47105 split_view_drag_indicators_ =
106 std::make_unique<SplitViewDragIndicators>(root_window_);
Yichen Zhoub81bae012021-04-09 23:18:10107
108 tab_dragging_recorder_ = CreatePresentationTimeHistogramRecorder(
109 source_window_->layer()->GetCompositor(),
110 kTabDraggingInTabletModeHistogram,
111 kTabDraggingInTabletModeMaxLatencyHistogram);
Collin Bakerc6fae372020-05-01 23:37:47112}
113
Collin Baker832c7932020-05-19 22:18:41114TabDragDropDelegate::~TabDragDropDelegate() {
Yichen Zhoub81bae012021-04-09 23:18:10115 tab_dragging_recorder_.reset();
116
Antonio Gomes5ec1a5a2023-01-19 02:55:50117 if (!source_window_) {
118 return;
119 }
120
121 source_window_->RemoveObserver(this);
122
Antonio Gomes0012f7c2022-07-08 19:28:59123 if (source_window_->is_destroying())
124 return;
125
Collin Baker832c7932020-05-19 22:18:41126 if (!source_window_->GetProperty(kIsSourceWindowForDrag))
127 return;
128
129 // If we didn't drop to a new window, we must restore the original window.
130 RestoreSourceWindowBounds();
131 source_window_->ClearProperty(kIsSourceWindowForDrag);
132}
Collin Bakerc6fae372020-05-01 23:37:47133
134void TabDragDropDelegate::DragUpdate(const gfx::Point& location_in_screen) {
135 const gfx::Rect area =
136 screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer(
137 root_window_);
138
Michele Fan11c9ad842023-12-08 01:27:26139 SnapPosition snap_position = ash::GetSnapPositionForLocation(
140 Shell::GetPrimaryRootWindow(), location_in_screen,
141 start_location_in_screen_,
142 /*snap_distance_from_edge=*/kDistanceFromEdgeDp,
143 /*minimum_drag_distance=*/kMinimumDragToSnapDistanceDp,
144 /*horizontal_edge_inset=*/area.width() *
145 kHighlightScreenPrimaryAxisRatio +
146 kHighlightScreenEdgePaddingDp,
147 /*vertical_edge_inset=*/area.height() * kHighlightScreenPrimaryAxisRatio +
148 kHighlightScreenEdgePaddingDp);
Yuheng Huangeeff78612022-04-27 20:57:01149 if (ShouldPreventSnapToTheEdge(location_in_screen))
Michele Fan11c9ad842023-12-08 01:27:26150 snap_position = SnapPosition::kNone;
Yuheng Huangeeff78612022-04-27 20:57:01151
Collin Bakerc6fae372020-05-01 23:37:47152 split_view_drag_indicators_->SetWindowDraggingState(
153 SplitViewDragIndicators::ComputeWindowDraggingState(
154 true, SplitViewDragIndicators::WindowDraggingState::kFromTop,
155 snap_position));
156
Yuheng Huange141ef22021-07-07 18:03:51157 UpdateSourceWindowBoundsIfNecessary(snap_position, location_in_screen);
Yichen Zhoub81bae012021-04-09 23:18:10158
159 tab_dragging_recorder_->RequestNext();
Collin Bakerc6fae372020-05-01 23:37:47160}
161
Antonio Gomes4c8353e2021-10-13 13:34:08162void TabDragDropDelegate::DropAndDeleteSelf(
163 const gfx::Point& location_in_screen,
164 const ui::OSExchangeData& drop_data) {
Yichen Zhoub81bae012021-04-09 23:18:10165 tab_dragging_recorder_.reset();
166
Antonio Gomes40900a312021-10-07 10:41:09167 auto closure = base::BindOnce(&TabDragDropDelegate::OnNewBrowserWindowCreated,
Antonio Gomes4c8353e2021-10-13 13:34:08168 base::Owned(this), location_in_screen);
Antonio Gomes8601b5c732021-10-14 15:41:47169 NewWindowDelegate::GetPrimary()->NewWindowForDetachingTab(
Antonio Gomes40900a312021-10-07 10:41:09170 source_window_, drop_data, std::move(closure));
171}
172
Antonio Gomes5ec1a5a2023-01-19 02:55:50173void TabDragDropDelegate::OnWindowDestroying(aura::Window* window) {
174 if (source_window_ == window) {
175 windows_hider_.reset();
176 source_window_->RemoveObserver(this);
177 source_window_ = nullptr;
178 }
179}
180
Antonio Gomes40900a312021-10-07 10:41:09181void TabDragDropDelegate::OnNewBrowserWindowCreated(
182 const gfx::Point& location_in_screen,
183 aura::Window* new_window) {
Xiyuan Xia6f93c652023-04-06 19:43:48184 // `source_window_` could reset to nullptr during the drag.
185 if (!source_window_) {
186 DCHECK(!new_window);
187 return;
188 }
189
Antonio Gomes519ab0a82022-01-10 21:03:21190 auto is_lacros = IsLacrosWindow(source_window_);
Yuheng Huang1b2d34012022-05-12 17:24:39191
192 // https://crbug.com/1286203:
193 // It's possible new window is created when the dragged WebContents
194 // closes itself during the drag session.
195 if (!new_window) {
Georg Neis16bb8912023-08-11 03:41:30196 if (is_lacros && !crosapi::lacros_startup_state::IsLacrosEnabled()) {
197 LOG(ERROR) << "New browser window creation for tab detaching failed.\n"
198 << "Check whether Lacros is enabled";
Yuheng Huang1b2d34012022-05-12 17:24:39199 }
Antonio Gomes519ab0a82022-01-10 21:03:21200 return;
201 }
Collin Bakerc6fae372020-05-01 23:37:47202
Collin Bakeraec43872020-05-07 20:28:57203 const gfx::Rect area =
204 screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer(
205 root_window_);
206
Michele Fan11c9ad842023-12-08 01:27:26207 SnapPosition snap_position_in_snapping_zone = ash::GetSnapPosition(
208 root_window_, new_window, location_in_screen, start_location_in_screen_,
209 /*snap_distance_from_edge=*/kDistanceFromEdgeDp,
210 /*minimum_drag_distance=*/kMinimumDragToSnapDistanceDp,
211 /*horizontal_edge_inset=*/area.width() *
212 kHighlightScreenPrimaryAxisRatio +
213 kHighlightScreenEdgePaddingDp,
214 /*vertical_edge_inset=*/area.height() * kHighlightScreenPrimaryAxisRatio +
215 kHighlightScreenEdgePaddingDp);
Yuheng Huangeeff78612022-04-27 20:57:01216 if (ShouldPreventSnapToTheEdge(location_in_screen))
Michele Fan11c9ad842023-12-08 01:27:26217 snap_position_in_snapping_zone = SnapPosition::kNone;
Collin Bakeraec43872020-05-07 20:28:57218
Michele Fan11c9ad842023-12-08 01:27:26219 if (snap_position_in_snapping_zone == SnapPosition::kNone) {
Collin Baker832c7932020-05-19 22:18:41220 RestoreSourceWindowBounds();
Lei Zhangf2166c62022-09-17 02:03:11221 }
Collin Baker832c7932020-05-19 22:18:41222
223 // This must be done after restoring the source window's bounds since
224 // otherwise the SetBounds() call may have no effect.
225 source_window_->ClearProperty(kIsSourceWindowForDrag);
226
Yuheng Huang098abd4b2021-07-07 19:32:30227 SplitViewController* const split_view_controller =
228 SplitViewController::Get(new_window);
229
230 // If it's already in split view mode, either snap the new window
231 // to the left or the right depending on the drop location.
232 const bool in_split_view_mode = split_view_controller->InSplitViewMode();
Michele Fan11c9ad842023-12-08 01:27:26233 SnapPosition snap_position = snap_position_in_snapping_zone;
Yuheng Huang098abd4b2021-07-07 19:32:30234 if (in_split_view_mode) {
235 snap_position =
236 split_view_controller->ComputeSnapPosition(location_in_screen);
237 }
238
Michele Fan11c9ad842023-12-08 01:27:26239 if (snap_position == SnapPosition::kNone) {
Collin Bakeraec43872020-05-07 20:28:57240 return;
Michele Fan11c9ad842023-12-08 01:27:26241 }
Collin Bakeraec43872020-05-07 20:28:57242
Yuheng Huangca5a28d2022-02-11 21:55:19243 OverviewSession* overview_session = GetOverviewSession();
244 // If overview session is present on the other side and the new window is
245 // about to snap to that side but not in the snapping zone then drop the new
246 // window into overview.
247 if (overview_session &&
Michele Fan11c9ad842023-12-08 01:27:26248 snap_position_in_snapping_zone == SnapPosition::kNone &&
Yuheng Huangca5a28d2022-02-11 21:55:19249 split_view_controller->GetPositionOfSnappedWindow(source_window_) !=
250 snap_position) {
251 overview_session->MergeWindowIntoOverviewForWebUITabStrip(new_window);
252 } else {
253 split_view_controller->SnapWindow(new_window, snap_position,
Xiaoqian Dai1fbf0102023-04-29 00:39:04254 WindowSnapActionSource::kDragTabToSnap,
Yuheng Huangca5a28d2022-02-11 21:55:19255 /*activate_window=*/true);
256 }
Collin Baker512a9d4e2020-05-12 19:34:33257
Yuheng Huang098abd4b2021-07-07 19:32:30258 // Do not snap the source window if already in split view mode.
259 if (in_split_view_mode)
260 return;
261
Collin Baker512a9d4e2020-05-12 19:34:33262 // The tab drag source window is the last window the user was
263 // interacting with. When dropping into split view, it makes the most
264 // sense to snap this window to the opposite side. Do this.
Michele Fan11c9ad842023-12-08 01:27:26265 SnapPosition opposite_position = (snap_position == SnapPosition::kPrimary)
266 ? SnapPosition::kSecondary
267 : SnapPosition::kPrimary;
Collin Baker512a9d4e2020-05-12 19:34:33268
269 // |source_window_| is itself a child window of the browser since it
270 // hosts web content (specifically, the tab strip WebUI). Snap its
271 // toplevel window which is the browser window.
Xiaoqian Dai1fbf0102023-04-29 00:39:04272 split_view_controller->SnapWindow(source_window_, opposite_position,
273 WindowSnapActionSource::kDragTabToSnap);
Collin Baker832c7932020-05-19 22:18:41274}
275
Yuheng Huangeeff78612022-04-27 20:57:01276bool TabDragDropDelegate::ShouldPreventSnapToTheEdge(
277 const gfx::Point& location_in_screen) {
278 SplitViewController* const split_view_controller =
279 SplitViewController::Get(source_window_);
280 return !split_view_controller->InSplitViewMode() &&
sophiewene2e8abd2023-12-05 18:04:12281 IsLayoutHorizontal(source_window_) &&
Yuheng Huangeeff78612022-04-27 20:57:01282 location_in_screen.y() <
283 Shell::Get()->shell_delegate()->GetBrowserWebUITabStripHeight();
284}
285
Collin Baker832c7932020-05-19 22:18:41286void TabDragDropDelegate::UpdateSourceWindowBoundsIfNecessary(
Michele Fan11c9ad842023-12-08 01:27:26287 SnapPosition candidate_snap_position,
Yuheng Huange141ef22021-07-07 18:03:51288 const gfx::Point& location_in_screen) {
Collin Baker832c7932020-05-19 22:18:41289 SplitViewController* const split_view_controller =
290 SplitViewController::Get(source_window_);
291
292 if (split_view_controller->IsWindowInSplitView(source_window_))
293 return;
294
295 if (!windows_hider_) {
Xiaoqian Daice2f7842023-03-17 20:39:31296 windows_hider_ = std::make_unique<TabDragDropWindowsHider>(source_window_);
Collin Baker832c7932020-05-19 22:18:41297 }
298
299 gfx::Rect new_source_window_bounds;
Michele Fan11c9ad842023-12-08 01:27:26300 if (candidate_snap_position == SnapPosition::kNone) {
Collin Baker832c7932020-05-19 22:18:41301 const gfx::Rect area =
302 screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer(
303 root_window_);
304 new_source_window_bounds = area;
Yuheng Huange141ef22021-07-07 18:03:51305
306 // Only shrink the window when the tab is dragged out of WebUI tab strip.
307 if (location_in_screen.y() >
308 Shell::Get()->shell_delegate()->GetBrowserWebUITabStripHeight()) {
309 new_source_window_bounds.ClampToCenteredSize(
310 gfx::Size(area.width() * kSourceWindowScale,
311 area.height() * kSourceWindowScale));
312 }
Collin Baker832c7932020-05-19 22:18:41313 } else {
Michele Fan11c9ad842023-12-08 01:27:26314 const SnapPosition opposite_position =
315 (candidate_snap_position == SnapPosition::kPrimary)
316 ? SnapPosition::kSecondary
317 : SnapPosition::kPrimary;
Collin Baker832c7932020-05-19 22:18:41318 new_source_window_bounds =
319 SplitViewController::Get(source_window_)
Michele Fan3c6da022023-12-09 00:52:45320 ->GetSnappedWindowBoundsInScreen(
321 opposite_position, source_window_,
322 window_util::GetSnapRatioForWindow(source_window_));
Collin Baker832c7932020-05-19 22:18:41323 }
324 wm::ConvertRectFromScreen(source_window_->parent(),
325 &new_source_window_bounds);
326
327 if (new_source_window_bounds != source_window_->GetTargetBounds()) {
328 ui::ScopedLayerAnimationSettings settings(
329 source_window_->layer()->GetAnimator());
330 settings.SetPreemptionStrategy(
331 ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
332 source_window_->SetBounds(new_source_window_bounds);
333 }
334}
335
336void TabDragDropDelegate::RestoreSourceWindowBounds() {
337 if (SplitViewController::Get(source_window_)
Sammie Quonba51e0d02023-04-11 19:54:53338 ->IsWindowInSplitView(source_window_)) {
Collin Baker832c7932020-05-19 22:18:41339 return;
Sammie Quonba51e0d02023-04-11 19:54:53340 }
341
342 auto* window_state = WindowState::Get(source_window_);
343 if (window_state->IsFloated()) {
344 // This will notify `FloatController` to find the ideal floated window
345 // bounds in tablet mode.
346 TabletModeWindowState::UpdateWindowPosition(
347 window_state, WindowState::BoundsChangeAnimationType::kNone);
348 return;
349 }
Collin Baker832c7932020-05-19 22:18:41350
351 const gfx::Rect area =
352 screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer(
353 root_window_);
354
355 source_window_->SetBounds(area);
Collin Bakerc6fae372020-05-01 23:37:47356}
357
358} // namespace ash