| // 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/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, &drag_info.data_endpoint, 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()->window_tree_host_manager()->AddObserver(this); |
| } |
| |
| DragDropController::~DragDropController() { |
| Shell::Get()->window_tree_host_manager()->RemoveObserver(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) { |
| // ET_MOUSE_CAPTURE_CHANGED event does not have a location that can |
| // be used to locate a translated target. |
| if (event->type() != ui::ET_MOUSE_CAPTURE_CHANGED) |
| DragCancel(); |
| event->StopPropagation(); |
| return; |
| } |
| |
| auto translated_event = ConvertEvent(translated_target, *event); |
| switch (translated_event->type()) { |
| case ui::ET_MOUSE_DRAGGED: |
| DragUpdate(translated_target, *translated_event.get()); |
| break; |
| case ui::ET_MOUSE_RELEASED: |
| 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::ET_TOUCH_CANCELLED) |
| 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::ET_GESTURE_SCROLL_UPDATE: |
| DragUpdate(translated_target, *translated_event); |
| break; |
| case ui::ET_GESTURE_SCROLL_END: |
| case ui::ET_SCROLL_FLING_START: |
| Drop(translated_target, *translated_event); |
| break; |
| case ui::ET_GESTURE_LONG_TAP: |
| // 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::OnDisplayConfigurationChanging() { |
| // 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 |