// Copyright 2012 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/drag_drop_controller.h"
#include <memory>
#include <utility>
#include "ash/drag_drop/drag_image_view.h"
#include "ash/drag_drop/toplevel_window_drag_delegate.h"
#include "ash/shell.h"
#include "ash/wm/window_util.h"
#include "base/functional/bind.h"
#include "base/run_loop.h"
#include "base/task/single_thread_task_runner.h"
#include "base/threading/hang_watcher.h"
#include "ui/aura/client/capture_client.h"
#include "ui/aura/client/drag_drop_client_observer.h"
#include "ui/aura/client/drag_drop_delegate.h"
#include "ui/aura/window.h"
#include "ui/aura/window_event_dispatcher.h"
#include "ui/aura/window_tracker.h"
#include "ui/base/cursor/mojom/cursor_type.mojom-shared.h"
#include "ui/base/data_transfer_policy/data_transfer_endpoint.h"
#include "ui/base/data_transfer_policy/data_transfer_policy_controller.h"
#include "ui/base/dragdrop/drag_drop_types.h"
#include "ui/base/dragdrop/mojom/drag_drop_types.mojom.h"
#include "ui/base/dragdrop/os_exchange_data.h"
#include "ui/base/dragdrop/os_exchange_data_provider.h"
#include "ui/base/hit_test.h"
#include "ui/display/manager/display_manager.h"
#include "ui/events/event.h"
#include "ui/events/event_utils.h"
#include "ui/gfx/animation/animation_delegate_notifier.h"
#include "ui/gfx/animation/linear_animation.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/views/animation/animation_delegate_views.h"
#include "ui/views/widget/native_widget_aura.h"
#include "ui/wm/core/coordinate_conversion.h"
namespace ash {
namespace {
using ::ui::mojom::DragOperation;
// The duration of the drag cancel animation in millisecond.
constexpr base::TimeDelta kCancelAnimationDuration = base::Milliseconds(250);
constexpr base::TimeDelta kTouchCancelAnimationDuration =
base::Milliseconds(20);
// The frame rate of the drag cancel animation in hertz.
const int kCancelAnimationFrameRate = 60;
// For touch initiated dragging, we scale and shift drag image by the following:
static const float kTouchDragImageScale = 1.2f;
static const int kTouchDragImageVerticalOffset = -25;
// Adjusts the drag image bounds such that the new bounds are scaled by |scale|
// and translated by the |drag_image_offset| and additional |vertical_offset|.
gfx::Rect AdjustDragImageBoundsForScaleAndOffset(
const gfx::Rect& drag_image_bounds,
int vertical_offset,
float scale,
gfx::Vector2d* drag_image_offset) {
gfx::Point final_origin = drag_image_bounds.origin();
gfx::SizeF final_size = gfx::SizeF(drag_image_bounds.size());
final_size.Scale(scale);
drag_image_offset->set_x(drag_image_offset->x() * scale);
drag_image_offset->set_y(drag_image_offset->y() * scale);
int total_x_offset = drag_image_offset->x();
int total_y_offset = drag_image_offset->y() - vertical_offset;
final_origin.Offset(-total_x_offset, -total_y_offset);
return gfx::ToEnclosingRect(
gfx::RectF(gfx::PointF(final_origin), final_size));
}
void DropIfAllowed(const ui::OSExchangeData* drag_data,
aura::client::DragUpdateInfo& drag_info,
base::OnceClosure drop_cb) {
DCHECK(drag_data);
if (ui::DataTransferPolicyController::HasInstance()) {
ui::DataTransferPolicyController::Get()->DropIfAllowed(
(drag_data->GetSource() ? std::make_optional<ui::DataTransferEndpoint>(
*drag_data->GetSource())
: std::nullopt),
{drag_info.data_endpoint}, drag_data->GetFilenames(),
std::move(drop_cb));
} else {
std::move(drop_cb).Run();
}
}
std::unique_ptr<ui::LocatedEvent> ConvertEvent(aura::Window* target,
const ui::LocatedEvent& event) {
gfx::Point target_location = event.location();
aura::Window::ConvertPointToTarget(static_cast<aura::Window*>(event.target()),
target, &target_location);
gfx::Point target_root_location = event.location();
aura::Window* target_root = target->GetRootWindow();
aura::Window::ConvertPointToTarget(static_cast<aura::Window*>(event.target()),
target_root, &target_root_location);
int changed_button_flags = 0;
if (event.IsMouseEvent())
changed_button_flags = event.AsMouseEvent()->changed_button_flags();
return std::make_unique<ui::MouseEvent>(
event.type(), target_location, target_root_location,
ui::EventTimeForNow(), event.flags(), changed_button_flags);
}
} // namespace
////////////////////////////////////////////////////////////////////////////////
// DragDropController, public:
DragDropController::DragDropController() {
Shell::Get()->AddPreTargetHandler(this, ui::EventTarget::Priority::kSystem);
Shell::Get()->display_manager()->AddDisplayManagerObserver(this);
}
DragDropController::~DragDropController() {
Shell::Get()->display_manager()->RemoveDisplayManagerObserver(this);
Shell::Get()->RemovePreTargetHandler(this);
Cleanup();
if (cancel_animation_)
cancel_animation_->End();
drag_image_widget_.reset();
}
bool DragDropController::IsDragDropCompleted() {
return drag_drop_completed_;
}
DragOperation DragDropController::StartDragAndDrop(
std::unique_ptr<ui::OSExchangeData> data,
aura::Window* root_window,
aura::Window* source_window,
const gfx::Point& screen_location,
int allowed_operations,
ui::mojom::DragEventSource source) {
if (!enabled_ || IsDragDropInProgress())
return DragOperation::kNone;
weak_factory_.InvalidateWeakPtrs();
const ui::OSExchangeDataProvider* provider = &data->provider();
// We do not support touch drag/drop without a drag image, unless it is a tab
// drag/drop.
if (source == ui::mojom::DragEventSource::kTouch &&
(!allow_no_image_touch_drag_for_test_ &&
provider->GetDragImage().size().IsEmpty()) &&
!toplevel_window_drag_delegate_) {
return DragOperation::kNone;
}
// Never consider the current scope as hung. The hang watching deadline (if
// any) is not valid since the user can take unbounded time to complete the
// drag.
base::HangWatcher::InvalidateActiveExpectations();
operation_ = DragOperation::kNone;
current_drag_event_source_ = source;
capture_delegate_ = nullptr;
const bool is_touch_source = source == ui::mojom::DragEventSource::kTouch;
bool touch_capture_attempted = false;
// When an extended drag is started, a capture window will be created to
// handle moving gestures between different wl surfaces to support dragging
// chrome tabs into and out of browsers.
if (is_touch_source && toplevel_window_drag_delegate_) {
touch_capture_attempted = true;
if (toplevel_window_drag_delegate_->TakeCapture(
root_window, source_window,
base::BindRepeating(&DragDropController::CancelIfInProgress,
base::Unretained(this)),
ui::TransferTouchesBehavior::kCancel)) {
capture_delegate_ = toplevel_window_drag_delegate_;
}
}
// |drag_source_window_| and |pending_long_tap_| could be non-null if a new
// drag starts while cancel animation or forwarding long tap event is ongoing.
// In this case, forwarding long tap event is aborted and related state should
// be cleaned up.
CleanupPendingLongTap();
drag_source_window_ = source_window;
if (drag_source_window_)
drag_source_window_->AddObserver(this);
drag_drop_completed_ = false;
drag_data_ = std::move(data);
allowed_operations_ = allowed_operations;
current_drag_info_ = aura::client::DragUpdateInfo();
start_location_ = screen_location;
current_location_ = screen_location;
// Ends cancel animation if it's in progress.
// This should happen before setting drag image because it refers to the drag
// image.
if (cancel_animation_)
cancel_animation_->End();
cancel_animation_.reset();
SetDragImage(provider->GetDragImage(), provider->GetDragImageOffset());
drag_window_ = nullptr;
for (aura::client::DragDropClientObserver& observer : observers_)
observer.OnDragStarted();
if (toplevel_window_drag_delegate_) {
toplevel_window_drag_delegate_->OnToplevelWindowDragStarted(
gfx::PointF(start_location_), source, drag_source_window_);
}
if (TabDragDropDelegate::IsChromeTabDrag(*drag_data_)) {
// TODO(aluh): Figure out why this allocation is outside the inner if-block.
DCHECK(!tab_drag_drop_delegate_);
tab_drag_drop_delegate_ = std::make_unique<TabDragDropDelegate>(
root_window, drag_source_window_, start_location_);
if (drag_image_widget_) {
static_cast<DragImageView*>(drag_image_widget_->GetContentsView())
->SetTouchDragOperationHintOff();
}
// Avoid taking capture twice.
if (is_touch_source && !touch_capture_attempted) {
touch_capture_attempted = true;
if (tab_drag_drop_delegate_->TakeCapture(
root_window, source_window,
base::BindRepeating(&DragDropController::CancelIfInProgress,
base::Unretained(this)),
ui::TransferTouchesBehavior::kDontCancel)) {
capture_delegate_ = tab_drag_drop_delegate_.get();
}
}
}
// If touch is not captured by either extended drag nor tab drag, start
// a normal drag-and-drop using DragDropCaptureDelegate.
if (is_touch_source && !touch_capture_attempted) {
touch_capture_attempted = true;
// For other type of touch drag, use normal DDCaptureDelegate;
touch_drag_drop_delegate_ = std::make_unique<DragDropCaptureDelegate>();
if (touch_drag_drop_delegate_->TakeCapture(
root_window, source_window,
base::BindRepeating(&DragDropController::CancelIfInProgress,
base::Unretained(this)),
ui::TransferTouchesBehavior::kDontCancel)) {
capture_delegate_ = touch_drag_drop_delegate_.get();
}
}
if (touch_capture_attempted && !capture_delegate_) {
Cleanup();
} else {
if (test_loop_closure_) {
while (!quit_closure_.is_null())
test_loop_closure_.Run();
} else {
base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed);
quit_closure_ = run_loop.QuitClosure();
run_loop.Run();
}
}
if (!will_forward_long_tap_) {
// If drag cancel animation or long tap event forwarding is running, this
// cleanup is done when the long tap event forwarding is done, or when a new
// drag is started.
// A check to catch an UAF issue like crbug.com/1282480 on non asan build.
DCHECK(!drag_source_window_ || !drag_source_window_->is_destroying());
CleanupPendingLongTap();
}
return operation_;
}
void DragDropController::SetDragImage(const gfx::ImageSkia& image,
const gfx::Vector2d& image_offset) {
if (image.size().IsEmpty()) {
drag_image_widget_.reset();
drag_image_final_bounds_for_cancel_animation_ = gfx::Rect();
drag_image_offset_ = gfx::Vector2d();
return;
}
auto source = current_drag_event_source_;
auto* source_window = drag_source_window_.get();
float drag_image_scale = 1;
int drag_image_vertical_offset = 0;
if (source == ui::mojom::DragEventSource::kTouch) {
drag_image_scale = kTouchDragImageScale;
drag_image_vertical_offset = kTouchDragImageVerticalOffset;
}
drag_image_final_bounds_for_cancel_animation_ =
gfx::Rect(start_location_ - image_offset, image.size());
// Only create `drag_image_widget_` if it doesn't exist. This prevents the
// case when dragging a webui tab in lacros keeps creating fresh
// `drag_image_widget_` with kTouch while it should have been set as kMouse to
// avoid drag hint. See crbug.com/1384469.
if (!drag_image_widget_) {
drag_image_widget_ =
DragImageView::Create(source_window->GetRootWindow(), source);
}
DragImageView* drag_image =
static_cast<DragImageView*>(drag_image_widget_->GetContentsView());
drag_image->SetImage(image);
drag_image_offset_ = image_offset;
gfx::Rect drag_image_bounds(current_location_,
drag_image->GetPreferredSize());
drag_image_bounds = AdjustDragImageBoundsForScaleAndOffset(
drag_image_bounds, drag_image_vertical_offset, drag_image_scale,
&drag_image_offset_);
drag_image->SetBoundsInScreen(drag_image_bounds);
drag_image->SetWidgetVisible(true);
if (source == ui::mojom::DragEventSource::kTouch) {
drag_image->SetTouchDragOperationHintPosition(
gfx::Point(drag_image_offset_.x(),
drag_image_offset_.y() + drag_image_vertical_offset));
}
}
void DragDropController::SetLoopClosureForTesting(
TestLoopClosure closure,
base::OnceClosure quit_closure) {
test_loop_closure_ = closure;
quit_closure_ = std::move(quit_closure);
}
void DragDropController::SetDisableNestedLoopForTesting(bool disable) {
nested_loop_disabled_for_testing_ = disable;
if (disable) {
base::OnceClosure quit_closure;
SetLoopClosureForTesting(base::DoNothing(), std::move(quit_closure));
} else {
test_loop_closure_.Reset();
quit_closure_.Reset();
}
}
void DragDropController::DragCancel() {
DCHECK(enabled_);
DoDragCancel(kCancelAnimationDuration);
}
bool DragDropController::IsDragDropInProgress() {
return !!drag_data_;
}
void DragDropController::AddObserver(
aura::client::DragDropClientObserver* observer) {
observers_.AddObserver(observer);
}
void DragDropController::RemoveObserver(
aura::client::DragDropClientObserver* observer) {
observers_.RemoveObserver(observer);
}
void DragDropController::OnKeyEvent(ui::KeyEvent* event) {
if (IsDragDropInProgress() && event->key_code() == ui::VKEY_ESCAPE) {
DragCancel();
event->StopPropagation();
}
}
void DragDropController::OnMouseEvent(ui::MouseEvent* event) {
if (!IsDragDropInProgress())
return;
// If current drag session was not started by mouse, dont process this mouse
// event, but consume it so it does not interfere with current drag session.
if (current_drag_event_source_ != ui::mojom::DragEventSource::kMouse) {
event->StopPropagation();
return;
}
aura::Window* translated_target =
window_util::GetEventHandlerForEvent(*event);
if (!translated_target) {
// EventType::kMouseCaptureChanged event does not have a location that can
// be used to locate a translated target.
if (event->type() != ui::EventType::kMouseCaptureChanged) {
DragCancel();
}
event->StopPropagation();
return;
}
auto translated_event = ConvertEvent(translated_target, *event);
switch (translated_event->type()) {
case ui::EventType::kMouseDragged:
DragUpdate(translated_target, *translated_event.get());
break;
case ui::EventType::kMouseReleased:
Drop(translated_target, *translated_event.get());
break;
default:
// We could also reach here because RootWindow may sometimes generate a
// bunch of fake mouse events
// (aura::RootWindow::PostMouseMoveEventAfterWindowChange).
break;
}
if (toplevel_window_drag_delegate_)
toplevel_window_drag_delegate_->OnToplevelWindowDragEvent(event);
event->StopPropagation();
}
void DragDropController::OnTouchEvent(ui::TouchEvent* event) {
if (!IsDragDropInProgress())
return;
// If current drag session was not started by touch, dont process this touch
// event, but consume it so it does not interfere with current drag session.
if (current_drag_event_source_ != ui::mojom::DragEventSource::kTouch)
event->StopPropagation();
if (event->handled())
return;
if (event->type() == ui::EventType::kTouchCancelled) {
DragCancel();
}
}
void DragDropController::OnGestureEvent(ui::GestureEvent* event) {
if (!IsDragDropInProgress())
return;
// If current drag session was not started by touch, dont process this event
// but consume it so it does not interfere with current drag session.
if (current_drag_event_source_ != ui::mojom::DragEventSource::kTouch) {
event->StopPropagation();
return;
}
// Apply kTouchDragImageVerticalOffset to the location, if it is not a tab
// drag/drop.
ui::GestureEvent touch_offset_event(*event,
static_cast<aura::Window*>(nullptr),
static_cast<aura::Window*>(nullptr));
if (!toplevel_window_drag_delegate_) {
gfx::PointF touch_offset_location = touch_offset_event.location_f();
gfx::PointF touch_offset_root_location =
touch_offset_event.root_location_f();
touch_offset_location.Offset(0, kTouchDragImageVerticalOffset);
touch_offset_root_location.Offset(0, kTouchDragImageVerticalOffset);
touch_offset_event.set_location_f(touch_offset_location);
touch_offset_event.set_root_location_f(touch_offset_root_location);
}
aura::Window* translated_target;
if (capture_delegate_) {
translated_target = capture_delegate_->GetTarget(touch_offset_event);
} else {
ui::Event::DispatcherApi(&touch_offset_event).set_target(event->target());
translated_target =
window_util::GetEventHandlerForEvent(touch_offset_event);
}
if (!translated_target) {
DragCancel();
event->StopPropagation();
return;
}
std::unique_ptr<ui::LocatedEvent> translated_event;
if (capture_delegate_) {
translated_event =
capture_delegate_->ConvertEvent(translated_target, touch_offset_event);
DCHECK(translated_event);
} else {
translated_event = ConvertEvent(translated_target, touch_offset_event);
}
switch (event->type()) {
case ui::EventType::kGestureScrollUpdate:
DragUpdate(translated_target, *translated_event);
break;
case ui::EventType::kGestureScrollEnd:
case ui::EventType::kScrollFlingStart:
Drop(translated_target, *translated_event);
break;
case ui::EventType::kGestureLongTap:
// Ideally we would want to just forward this long tap event to the
// |drag_source_window_|. However, webkit does not accept events while a
// drag drop is still in progress. The drag drop ends only when the nested
// message loop ends. Due to this, we have to defer forwarding
// the long tap.
if (capture_delegate_) {
auto* capture_window =
static_cast<aura::Window*>(capture_delegate_->capture_window());
CHECK(capture_window);
pending_long_tap_ = std::make_unique<ui::GestureEvent>(
*event, capture_window,
static_cast<aura::Window*>(drag_source_window_));
} else {
pending_long_tap_ = event->Clone();
}
DoDragCancel(kTouchCancelAnimationDuration);
break;
default:
break;
}
if (toplevel_window_drag_delegate_)
toplevel_window_drag_delegate_->OnToplevelWindowDragEvent(event);
event->StopPropagation();
}
void DragDropController::OnWindowDestroying(aura::Window* window) {
if (drag_window_ == window) {
aura::client::DragDropDelegate* delegate =
aura::client::GetDragDropDelegate(drag_window_);
if (delegate)
delegate->OnDragExited();
drag_window_->RemoveObserver(this);
drag_window_ = nullptr;
}
if (drag_source_window_ == window) {
if (drag_source_window_->HasObserver(this))
drag_source_window_->RemoveObserver(this);
drag_source_window_ = nullptr;
// TabDragDropDelegate dereferences |drag_source_window_| in its logic,
// and is meaningless without a valid instance of it.
tab_drag_drop_delegate_.reset();
}
}
////////////////////////////////////////////////////////////////////////////////
// DragDropController, protected:
gfx::LinearAnimation* DragDropController::CreateCancelAnimation(
base::TimeDelta duration,
int frame_rate,
gfx::AnimationDelegate* delegate) {
return new gfx::LinearAnimation(duration, frame_rate, delegate);
}
void DragDropController::DragUpdate(aura::Window* target,
const ui::LocatedEvent& event) {
ui::DropTargetEvent e(*drag_data_.get(), event.location_f(),
event.root_location_f(), allowed_operations_);
e.SetFlags(event.flags());
ui::Event::DispatcherApi(&e).set_target(target);
aura::client::DragUpdateInfo drag_info;
if (target != drag_window_) {
if (drag_window_) {
aura::client::DragDropDelegate* delegate =
aura::client::GetDragDropDelegate(drag_window_);
if (delegate)
delegate->OnDragExited();
if (drag_window_ != drag_source_window_)
drag_window_->RemoveObserver(this);
}
drag_window_ = target;
// We are already an observer of |drag_source_window_| so no need to add.
if (drag_window_ != drag_source_window_)
drag_window_->AddObserver(this);
aura::client::DragDropDelegate* delegate =
aura::client::GetDragDropDelegate(drag_window_);
if (delegate)
delegate->OnDragEntered(e);
} else {
aura::client::DragDropDelegate* delegate =
aura::client::GetDragDropDelegate(drag_window_);
if (delegate) {
drag_info = delegate->OnDragUpdated(e);
gfx::NativeCursor cursor = ui::mojom::CursorType::kNoDrop;
if (drag_info.drag_operation & ui::DragDropTypes::DRAG_COPY)
cursor = ui::mojom::CursorType::kCopy;
else if (drag_info.drag_operation & ui::DragDropTypes::DRAG_LINK)
cursor = ui::mojom::CursorType::kAlias;
else if (drag_info.drag_operation & ui::DragDropTypes::DRAG_MOVE)
cursor = ui::mojom::CursorType::kGrabbing;
Shell::Get()->cursor_manager()->SetCursor(cursor);
}
}
for (aura::client::DragDropClientObserver& observer : observers_)
observer.OnDragUpdated(e);
if (drag_info.drag_operation != current_drag_info_.drag_operation) {
for (aura::client::DragDropClientObserver& observer : observers_)
observer.OnDragActionsChanged(drag_info.drag_operation);
}
current_drag_info_ = drag_info;
gfx::Point root_location_in_screen = event.root_location();
::wm::ConvertPointToScreen(target->GetRootWindow(), &root_location_in_screen);
current_location_ = root_location_in_screen;
if (drag_image_widget_) {
DragImageView* drag_image =
static_cast<DragImageView*>(drag_image_widget_->GetContentsView());
drag_image->SetScreenPosition(root_location_in_screen - drag_image_offset_);
drag_image->SetTouchDragOperation(drag_info.drag_operation);
}
if (tab_drag_drop_delegate_) {
// TabDragDropDelegate assumes the root window doesn't change. Tab drags are
// only seen in tablet mode which precludes dragging between displays.
// DCHECK just to make sure.
DCHECK_EQ(target->GetRootWindow(), tab_drag_drop_delegate_->root_window());
tab_drag_drop_delegate_->DragUpdate(root_location_in_screen);
}
}
void DragDropController::Drop(aura::Window* target,
const ui::LocatedEvent& event) {
// We must guarantee that a target gets a OnDragEntered before Drop. WebKit
// depends on not getting a Drop without DragEnter. This behavior is
// consistent with drag/drop on other platforms.
if (target != drag_window_)
DragUpdate(target, event);
DCHECK(target == drag_window_);
Shell::Get()->cursor_manager()->SetCursor(ui::mojom::CursorType::kPointer);
aura::client::DragDropDelegate* delegate =
aura::client::GetDragDropDelegate(target);
aura::client::DragDropDelegate::DropCallback delegate_drop_cb =
base::NullCallback();
ui::DropTargetEvent e(*drag_data_.get(), event.location_f(),
event.root_location_f(), allowed_operations_);
e.SetFlags(event.flags());
ui::Event::DispatcherApi(&e).set_target(target);
for (aura::client::DragDropClientObserver& observer : observers_) {
observer.OnDragCompleted(e);
}
if (delegate) {
delegate_drop_cb = delegate->GetDropCallback(e);
}
base::ScopedClosureRunner drag_cancel(base::BindOnce(
&DragDropController::DragCancel, weak_factory_.GetWeakPtr()));
gfx::Point drop_location_in_screen = event.root_location();
::wm::ConvertPointToScreen(target->GetRootWindow(), &drop_location_in_screen);
const bool is_tab_drag_drop = (tab_drag_drop_delegate_.get() != nullptr);
DCHECK_EQ(drag_window_, target);
DropIfAllowed(
drag_data_.get(), current_drag_info_,
base::BindOnce(&DragDropController::PerformDrop,
weak_factory_.GetWeakPtr(), drop_location_in_screen, e,
std::move(drag_data_), std::move(delegate_drop_cb),
std::move(tab_drag_drop_delegate_),
std::move(drag_cancel)));
Cleanup();
// Tab drag-n-drop should never be async.
if (is_tab_drag_drop)
DCHECK(!drag_image_widget_);
// If the drop is async and cancelled animation isn't running, reset
// |drag_image_widget_|.
if (!cancel_animation_)
drag_image_widget_.reset();
if (quit_closure_)
std::move(quit_closure_).Run();
}
////////////////////////////////////////////////////////////////////////////////
// DragDropController, private:
void DragDropController::AnimationEnded(const gfx::Animation* animation) {
cancel_animation_.reset();
cancel_animation_notifier_.reset();
drag_image_widget_.reset();
ScheduleForwardPendingLongTap();
}
void DragDropController::DoDragCancel(
base::TimeDelta drag_cancel_animation_duration) {
Shell::Get()->cursor_manager()->SetCursor(ui::mojom::CursorType::kPointer);
// |drag_window_| can be NULL if we have just started the drag and have not
// received any DragUpdates, or, if the |drag_window_| gets destroyed during
// a drag/drop.
aura::client::DragDropDelegate* delegate =
drag_window_ ? aura::client::GetDragDropDelegate(drag_window_) : nullptr;
if (delegate)
delegate->OnDragExited();
if (toplevel_window_drag_delegate_)
toplevel_window_drag_delegate_->OnToplevelWindowDragCancelled();
for (aura::client::DragDropClientObserver& observer : observers_) {
observer.OnDragCancelled();
}
Cleanup();
StartCanceledAnimation(drag_cancel_animation_duration);
if (quit_closure_)
std::move(quit_closure_).Run();
}
void DragDropController::AnimationProgressed(const gfx::Animation* animation) {
DCHECK(drag_image_widget_);
gfx::Rect current_bounds = animation->CurrentValueBetween(
drag_image_initial_bounds_for_cancel_animation_,
drag_image_final_bounds_for_cancel_animation_);
static_cast<DragImageView*>(drag_image_widget_->GetContentsView())
->SetBoundsInScreen(current_bounds);
}
void DragDropController::AnimationCanceled(const gfx::Animation* animation) {
AnimationEnded(animation);
}
void DragDropController::OnWillApplyDisplayChanges() {
// Abort in-progress drags if a monitor is added or removed because the drag
// image widget's container may be destroyed.
if (IsDragDropInProgress())
DragCancel();
}
void DragDropController::StartCanceledAnimation(
base::TimeDelta animation_duration) {
DCHECK(!cancel_animation_);
DCHECK(!will_forward_long_tap_);
if (pending_long_tap_)
will_forward_long_tap_ = true;
if (!drag_image_widget_) {
ScheduleForwardPendingLongTap();
return;
}
DragImageView* drag_image =
static_cast<DragImageView*>(drag_image_widget_->GetContentsView());
drag_image->SetTouchDragOperationHintOff();
drag_image_initial_bounds_for_cancel_animation_ =
drag_image->GetBoundsInScreen();
cancel_animation_notifier_ = std::make_unique<
gfx::AnimationDelegateNotifier<views::AnimationDelegateViews>>(
this, drag_image);
cancel_animation_.reset(
CreateCancelAnimation(animation_duration, kCancelAnimationFrameRate,
cancel_animation_notifier_.get()));
cancel_animation_->Start();
}
void DragDropController::ScheduleForwardPendingLongTap() {
if (!pending_long_tap_)
return;
// If not in a nested run loop, we can forward the long tap right now.
if (nested_loop_disabled_for_testing_) {
ForwardPendingLongTap();
return;
}
// See comment about this in OnGestureEvent().
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&DragDropController::ForwardPendingLongTap,
weak_factory_.GetWeakPtr()));
}
void DragDropController::ForwardPendingLongTap() {
if (drag_source_window_ && drag_source_window_->delegate()) {
drag_source_window_->delegate()->OnGestureEvent(
pending_long_tap_->AsGestureEvent());
DispatchGestureEndToWindow(drag_source_window_);
}
CleanupPendingLongTap();
}
void DragDropController::Cleanup() {
if (!capture_delegate_ && drag_source_window_ &&
current_drag_event_source_ == ui::mojom::DragEventSource::kMouse) {
drag_source_window_->ReleaseCapture();
}
// Do not remove observer if `drag_window_` is the same as
// `drag_source_window_`.
// `drag_source_window_` is still necessary to process long tab and the
// observer will be reset when `drag_source_window_` is destroyed.
if (drag_window_ && drag_window_ != drag_source_window_)
drag_window_->RemoveObserver(this);
drag_window_ = nullptr;
drag_drop_completed_ = true;
drag_data_.reset();
allowed_operations_ = 0;
tab_drag_drop_delegate_.reset();
touch_drag_drop_delegate_.reset();
capture_delegate_ = nullptr;
}
void DragDropController::CleanupPendingLongTap() {
pending_long_tap_.reset();
will_forward_long_tap_ = false;
if (drag_source_window_)
drag_source_window_->RemoveObserver(this);
drag_source_window_ = nullptr;
}
void DragDropController::PerformDrop(
const gfx::Point drop_location_in_screen,
ui::DropTargetEvent event,
std::unique_ptr<ui::OSExchangeData> drag_data,
aura::client::DragDropDelegate::DropCallback drop_cb,
std::unique_ptr<TabDragDropDelegate> tab_drag_drop_delegate,
base::ScopedClosureRunner cancel_drag_callback) {
// Event copy constructor dooesn't copy the target. That's why we set it here.
// DragDropController observes the `drag_window_`, so if it's destroyed, the
// target will be set to nullptr.
ui::Event::DispatcherApi(&event).set_target(drag_window_);
ui::OSExchangeData copied_data(drag_data->provider().Clone());
if (!!drop_cb) {
std::move(drop_cb).Run(
std::move(drag_data), operation_,
drag_image_widget_
? ::wm::RecreateLayers(drag_image_widget_->GetNativeWindow())
: nullptr);
}
if (operation_ == DragOperation::kNone && tab_drag_drop_delegate) {
// Release the ownership of object so that it can delete itself.
tab_drag_drop_delegate.release()->DropAndDeleteSelf(drop_location_in_screen,
copied_data);
// Override the drag event's drop effect as a move to inform the front-end
// that the tab or group was moved. Otherwise, the WebUI tab strip does
// not know that a drop resulted in a tab being moved and will temporarily
// visually return the tab to its original position. (crbug.com/1081905)
operation_ = DragOperation::kMove;
drag_image_widget_.reset();
} else if (operation_ == DragOperation::kNone) {
StartCanceledAnimation(kCancelAnimationDuration);
} else {
drag_image_widget_.reset();
}
if (toplevel_window_drag_delegate_) {
operation_ = toplevel_window_drag_delegate_->OnToplevelWindowDragDropped();
}
for (aura::client::DragDropClientObserver& observer : observers_) {
observer.OnDropCompleted(operation_);
}
// Drop completed, so no need to cancel the drop.
std::ignore = cancel_drag_callback.Release();
}
void DragDropController::CancelIfInProgress() {
if (IsDragDropInProgress())
DragCancel();
}
} // namespace ash