chromium/ash/drag_drop/drag_drop_controller_unittest.cc

// 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 <optional>

#include "ash/constants/ash_features.h"
#include "ash/drag_drop/drag_image_view.h"
#include "ash/drag_drop/mock_drag_drop_observer.h"
#include "ash/drag_drop/toplevel_window_drag_delegate.h"
#include "ash/public/cpp/test/test_new_window_delegate.h"
#include "ash/shell.h"
#include "ash/test/ash_test_base.h"
#include "ash/test_shell_delegate.h"
#include "ash/wm/splitview/split_view_controller.h"
#include "ash/wm/splitview/split_view_types.h"
#include "ash/wm/tablet_mode/tablet_mode_controller_test_api.h"
#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/memory/raw_ptr.h"
#include "base/notreached.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/bind.h"
#include "base/test/gmock_callback_support.h"
#include "testing/gmock/include/gmock/gmock.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/test/test_window_delegate.h"
#include "ui/aura/window_event_dispatcher.h"
#include "ui/aura/window_tree_host.h"
#include "ui/base/clipboard/clipboard.h"
#include "ui/base/clipboard/scoped_clipboard_writer.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/data_transfer_policy/mock_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/compositor/layer_tree_owner.h"
#include "ui/events/event.h"
#include "ui/events/event_utils.h"
#include "ui/events/gesture_detection/gesture_configuration.h"
#include "ui/events/gestures/gesture_types.h"
#include "ui/events/test/event_generator.h"
#include "ui/events/test/events_test_utils.h"
#include "ui/gfx/animation/linear_animation.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/point_conversions.h"
#include "ui/gfx/geometry/point_f.h"
#include "ui/gfx/image/image_skia_rep.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget.h"
#include "ui/wm/core/coordinate_conversion.h"

namespace ash {
namespace {

using ::base::test::RunOnceCallback;
using ::testing::_;
using ::testing::NiceMock;
using ::testing::Return;
using ::ui::mojom::DragOperation;

// Sets string and drag image for testing.
void SetDragData(OSExchangeData* data, bool with_image) {
  data->SetString(u"I am being dragged");
  if (with_image) {
    gfx::ImageSkiaRep image_rep(gfx::Size(10, 20), 1.0f);
    gfx::ImageSkia image_skia(image_rep);
    data->provider().SetDragImage(image_skia, gfx::Vector2d());
  }
}

std::unique_ptr<ui::OSExchangeData> CreateDragData(bool with_image) {
  auto data = std::make_unique<ui::OSExchangeData>();
  SetDragData(data.get(), with_image);
  return data;
}

// A simple view that makes sure RunShellDrag is invoked on mouse drag.
class DragTestView : public views::View {
 public:
  DragTestView() : views::View() { Reset(); }

  DragTestView(const DragTestView&) = delete;
  DragTestView& operator=(const DragTestView&) = delete;

  void Reset() {
    num_drag_enters_ = 0;
    num_drag_exits_ = 0;
    num_drag_updates_ = 0;
    num_drops_ = 0;
    drag_done_received_ = false;
    long_tap_received_ = false;
  }

  int VerticalDragThreshold() {
    return views::View::GetVerticalDragThreshold();
  }

  int HorizontalDragThreshold() {
    return views::View::GetHorizontalDragThreshold();
  }

  void OmitDragImage() { omit_drag_image_ = true; }

  int num_drag_enters_;
  int num_drag_exits_;
  int num_drag_updates_;
  int num_drops_;
  bool drag_done_received_;
  bool long_tap_received_;

 private:
  // View overrides:
  int GetDragOperations(const gfx::Point& press_pt) override {
    return ui::DragDropTypes::DRAG_COPY;
  }

  void WriteDragData(const gfx::Point& p, OSExchangeData* data) override {
    SetDragData(data, /*with_image=*/!omit_drag_image_);
  }

  bool OnMousePressed(const ui::MouseEvent& event) override { return true; }

  void OnGestureEvent(ui::GestureEvent* event) override {
    if (event->type() == ui::EventType::kGestureLongTap) {
      long_tap_received_ = true;
    }
    return;
  }

  bool GetDropFormats(
      int* formats,
      std::set<ui::ClipboardFormatType>* format_types) override {
    *formats = ui::OSExchangeData::STRING;
    return true;
  }

  bool CanDrop(const OSExchangeData& data) override { return true; }

  void OnDragEntered(const ui::DropTargetEvent& event) override {
    num_drag_enters_++;
  }

  int OnDragUpdated(const ui::DropTargetEvent& event) override {
    num_drag_updates_++;
    return ui::DragDropTypes::DRAG_COPY;
  }

  void OnDragExited() override { num_drag_exits_++; }

  DropCallback GetDropCallback(const ui::DropTargetEvent& event) override {
    return base::BindOnce(&DragTestView::PerformDrop, base::Unretained(this));
  }

  void OnDragDone() override { drag_done_received_ = true; }

  void PerformDrop(const ui::DropTargetEvent& event,
                   ui::mojom::DragOperation& output_drag_op,
                   std::unique_ptr<ui::LayerTreeOwner> drag_image_layer_owner) {
    num_drops_++;
    output_drag_op = DragOperation::kCopy;
  }

  bool omit_drag_image_ = false;
};

class CompletableLinearAnimation : public gfx::LinearAnimation {
 public:
  CompletableLinearAnimation(base::TimeDelta duration,
                             int frame_rate,
                             gfx::AnimationDelegate* delegate)
      : gfx::LinearAnimation(duration, frame_rate, delegate) {}

  CompletableLinearAnimation(const CompletableLinearAnimation&) = delete;
  CompletableLinearAnimation& operator=(const CompletableLinearAnimation&) =
      delete;

  void Complete() { Step(start_time() + duration()); }
};

class TestDragDropController : public DragDropController {
 public:
  TestDragDropController() : DragDropController() { Reset(); }

  TestDragDropController(const TestDragDropController&) = delete;
  TestDragDropController& operator=(const TestDragDropController&) = delete;

  void Reset() {
    drag_start_received_ = false;
    num_drag_updates_ = 0;
    drop_received_ = false;
    drag_canceled_ = false;
    drag_string_.reset();
  }

  DragOperation StartDragAndDrop(std::unique_ptr<ui::OSExchangeData> data,
                                 aura::Window* root_window,
                                 aura::Window* source_window,
                                 const gfx::Point& location,
                                 int allowed_operations,
                                 ui::mojom::DragEventSource source) override {
    drag_start_received_ = true;
    drag_string_ = data->GetString();
    return DragDropController::StartDragAndDrop(std::move(data), root_window,
                                                source_window, location,
                                                allowed_operations, source);
  }

  DragDropCaptureDelegate* get_capture_delegate() {
    return DragDropController::get_capture_delegate();
  }

  void DragUpdate(aura::Window* target,
                  const ui::LocatedEvent& event) override {
    DragDropController::DragUpdate(target, event);
    num_drag_updates_++;
  }

  void Drop(aura::Window* target, const ui::LocatedEvent& event) override {
    DragDropController::Drop(target, event);
    drop_received_ = true;
  }

  void DragCancel() override {
    DragDropController::DragCancel();
    drag_canceled_ = true;
  }

  gfx::LinearAnimation* CreateCancelAnimation(
      base::TimeDelta duration,
      int frame_rate,
      gfx::AnimationDelegate* delegate) override {
    return new CompletableLinearAnimation(duration, frame_rate, delegate);
  }

  void DoDragCancel(base::TimeDelta animation_duration) override {
    DragDropController::DoDragCancel(animation_duration);
    drag_canceled_ = true;
  }

  bool drag_start_received_;
  int num_drag_updates_;
  bool drop_received_;
  bool drag_canceled_;
  std::optional<std::u16string> drag_string_;
};

class TestObserver : public aura::client::DragDropClientObserver {
 public:
  enum class State { kNotInvoked, kDragStartedInvoked, kDragEndedInvoked };

  TestObserver() : state_(State::kNotInvoked) {}

  TestObserver(const TestObserver&) = delete;
  TestObserver& operator=(const TestObserver&) = delete;

  State state() const { return state_; }

  // aura::client::DragDropClientObserver
  void OnDragStarted() override {
    EXPECT_EQ(State::kNotInvoked, state_);
    state_ = State::kDragStartedInvoked;
  }

  void OnDragCompleted(const ui::DropTargetEvent& event) override {
    EXPECT_EQ(State::kDragStartedInvoked, state_);
    state_ = State::kDragEndedInvoked;
  }

 private:
  State state_;
};

class EventTargetTestDelegate : public aura::client::DragDropDelegate {
 public:
  enum class State {
    kNotInvoked,
    kDragEnteredInvoked,
    kDragUpdateInvoked,
    kPerformDropInvoked,
    kDragExitInvoked
  };

  explicit EventTargetTestDelegate(aura::Window* window) : window_(window) {}

  EventTargetTestDelegate(const EventTargetTestDelegate&) = delete;
  EventTargetTestDelegate& operator=(const EventTargetTestDelegate&) = delete;

  State state() const { return state_; }

  // aura::client::DragDropDelegate:
  void OnDragEntered(const ui::DropTargetEvent& event) override {
    EXPECT_EQ(State::kNotInvoked, state_);
    EXPECT_EQ(window_, event.target());
    state_ = State::kDragEnteredInvoked;
  }
  aura::client::DragUpdateInfo OnDragUpdated(
      const ui::DropTargetEvent& event) override {
    EXPECT_TRUE(State::kDragEnteredInvoked == state_ ||
                State::kDragUpdateInvoked == state_);
    EXPECT_EQ(window_, event.target());
    state_ = State::kDragUpdateInvoked;
    return aura::client::DragUpdateInfo(
        ui::DragDropTypes::DRAG_MOVE,
        ui::DataTransferEndpoint(ui::EndpointType::kDefault));
  }
  void OnDragExited() override {
    EXPECT_TRUE(State::kDragEnteredInvoked == state_ ||
                State::kDragUpdateInvoked == state_);
    state_ = State::kDragExitInvoked;
  }
  DropCallback GetDropCallback(const ui::DropTargetEvent& event) override {
    return base::BindOnce(&EventTargetTestDelegate::PerformDrop,
                          base::Unretained(this));
  }

 private:
  void PerformDrop(std::unique_ptr<ui::OSExchangeData> data,
                   ui::mojom::DragOperation& output_drag_op,
                   std::unique_ptr<ui::LayerTreeOwner> drag_image_layer_owner) {
    EXPECT_EQ(State::kDragUpdateInvoked, state_);

    state_ = State::kPerformDropInvoked;
    output_drag_op = DragOperation::kMove;
  }

  const raw_ptr<aura::Window, DanglingUntriaged> window_;
  State state_{State::kNotInvoked};
};

void AddViewToWidgetAndResize(views::Widget* widget, views::View* view) {
  if (!widget->GetContentsView())
    widget->SetContentsView(std::make_unique<views::View>());

  views::View* contents_view = widget->GetContentsView();
  contents_view->AddChildView(view);
  view->SetBounds(contents_view->width(), 0, 100, 100);
  gfx::Rect contents_view_bounds = contents_view->bounds();
  contents_view_bounds.Union(view->bounds());
  contents_view->SetBoundsRect(contents_view_bounds);
  widget->SetBounds(contents_view_bounds);
}

void DispatchGesture(ui::EventType gesture_type, gfx::Point location) {
  ui::GestureEventDetails event_details(gesture_type);
  ui::GestureEvent gesture_event(location.x(), location.y(), 0,
                                 ui::EventTimeForNow(), event_details);
  ui::EventSource* event_source =
      Shell::GetPrimaryRootWindow()->GetHost()->GetEventSource();
  ui::EventSourceTestApi event_source_test(event_source);
  ui::EventDispatchDetails details =
      event_source_test.SendEventToSink(&gesture_event);
  CHECK(!details.dispatcher_destroyed);
}

class TestToplevelWindowDragDelegate : public ToplevelWindowDragDelegate {
 public:
  enum class State {
    kNotInvoked,
    kDragStartedInvoked,
    kDragDroppedInvoked,
    kDragCancelledInvoked,
    kDragEventInvoked
  };

  TestToplevelWindowDragDelegate() = default;

  TestToplevelWindowDragDelegate(const TestToplevelWindowDragDelegate&) =
      delete;
  TestToplevelWindowDragDelegate& operator=(
      const TestToplevelWindowDragDelegate&) = delete;

  ~TestToplevelWindowDragDelegate() override = default;

  State state() const { return state_; }
  int events_forwarded() const { return events_forwarded_; }
  ui::mojom::DragEventSource source() const { return source_; }
  std::optional<gfx::PointF> current_location() const {
    return current_location_;
  }

  // ToplevelWindowDragDelegate:
  void OnToplevelWindowDragStarted(const gfx::PointF& start_location,
                                   ui::mojom::DragEventSource source,
                                   aura::Window* source_window) override {
    ASSERT_EQ(State::kNotInvoked, state_);
    ASSERT_TRUE(source_window);
    state_ = State::kDragStartedInvoked;
    current_location_.emplace(start_location);
    source_ = source;
    if (source == ui::mojom::DragEventSource::kMouse)
      source_window->SetCapture();
  }

  DragOperation OnToplevelWindowDragDropped() override {
    EXPECT_EQ(State::kDragStartedInvoked, state_);
    state_ = State::kDragDroppedInvoked;
    return DragOperation::kMove;
  }

  void OnToplevelWindowDragCancelled() override {
    EXPECT_EQ(State::kDragStartedInvoked, state_);
    state_ = State::kDragCancelledInvoked;
  }

  void OnToplevelWindowDragEvent(ui::LocatedEvent* event) override {
    ASSERT_TRUE(event);
    EXPECT_TRUE(current_location_.has_value());
    current_location_.emplace(event->root_location_f());
    events_forwarded_++;
    event->StopPropagation();
  }

 private:
  State state_ = State::kNotInvoked;
  int events_forwarded_ = 0;
  std::optional<gfx::PointF> current_location_;
  ui::mojom::DragEventSource source_;
};

}  // namespace

class MockShellDelegate : public TestShellDelegate {
 public:
  MockShellDelegate() = default;
  ~MockShellDelegate() override = default;

  MOCK_METHOD(bool, IsTabDrag, (const ui::OSExchangeData&), (override));
};

class MockNewWindowDelegate : public TestNewWindowDelegate {
 public:
  MockNewWindowDelegate() = default;
  ~MockNewWindowDelegate() override = default;

  MOCK_METHOD(void,
              NewWindowForDetachingTab,
              (aura::Window*,
               const ui::OSExchangeData&,
               NewWindowForDetachingTabCallback),
              (override));
};

class DragDropControllerTest : public AshTestBase {
 public:
  DragDropControllerTest()
      : AshTestBase(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}

  DragDropControllerTest(const DragDropControllerTest&) = delete;
  DragDropControllerTest& operator=(const DragDropControllerTest&) = delete;

  ~DragDropControllerTest() override = default;

  void SetUp() override {
    auto mock_new_window_delegate =
        std::make_unique<NiceMock<MockNewWindowDelegate>>();
    mock_new_window_delegate_ptr_ = mock_new_window_delegate.get();
    test_new_window_delegate_provider_ =
        std::make_unique<TestNewWindowDelegateProvider>(
            std::move(mock_new_window_delegate));

    auto mock_shell_delegate = std::make_unique<NiceMock<MockShellDelegate>>();
    mock_shell_delegate_ = mock_shell_delegate.get();
    AshTestBase::SetUp(std::move(mock_shell_delegate));

    drag_drop_controller_ = std::make_unique<TestDragDropController>();
    drag_drop_controller_->set_should_block_during_drag_drop(false);
    drag_drop_controller_->set_enabled(true);
    aura::client::SetDragDropClient(Shell::GetPrimaryRootWindow(),
                                    drag_drop_controller_.get());
  }

  void TearDown() override {
    aura::client::SetDragDropClient(Shell::GetPrimaryRootWindow(), NULL);
    drag_drop_controller_.reset();
    AshTestBase::TearDown();
  }

  aura::Window* GetDragWindow() { return drag_drop_controller_->drag_window_; }

  aura::Window* GetDragSourceWindow() {
    return drag_drop_controller_->drag_source_window_;
  }

  void SetDragSourceWindow(aura::Window* drag_source_window) {
    drag_drop_controller_->drag_source_window_ = drag_source_window;
    drag_source_window->AddObserver(drag_drop_controller_.get());
  }

  gfx::ImageSkia GetDragImage() {
    return static_cast<DragImageView*>(
               drag_drop_controller_->drag_image_widget_->GetContentsView())
        ->GetImage();
  }

  aura::Window* GetDragImageWindow() {
    return drag_drop_controller_->drag_image_widget_
               ? drag_drop_controller_->drag_image_widget_->GetNativeWindow()
               : nullptr;
  }

  MockShellDelegate* mock_shell_delegate() { return mock_shell_delegate_; }

  MockNewWindowDelegate* mock_new_window_delegate() {
    return mock_new_window_delegate_ptr_;
  }

  gfx::LinearAnimation* cancel_animation() {
    return drag_drop_controller_->cancel_animation_.get();
  }

  void CompleteCancelAnimation() {
    CompletableLinearAnimation* animation =
        static_cast<CompletableLinearAnimation*>(
            drag_drop_controller_->cancel_animation_.get());
    animation->Complete();
  }

 protected:
  std::unique_ptr<views::Widget> CreateFramelessWidget() {
    std::unique_ptr<views::Widget> widget = std::make_unique<views::Widget>();
    views::Widget::InitParams params(
        views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET,
        views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
    params.context = GetContext();
    widget->Init(std::move(params));
    widget->Show();
    return widget;
  }

  std::unique_ptr<TestDragDropController> drag_drop_controller_;
  raw_ptr<NiceMock<MockShellDelegate>, DanglingUntriaged> mock_shell_delegate_ =
      nullptr;

  std::unique_ptr<TestNewWindowDelegateProvider>
      test_new_window_delegate_provider_;
  raw_ptr<NiceMock<MockNewWindowDelegate>> mock_new_window_delegate_ptr_ =
      nullptr;

  bool quit_ = false;

  void RunWithClosure(base::RepeatingCallback<void(bool)> loop) {
    quit_ = false;

    drag_drop_controller_->SetLoopClosureForTesting(
        base::BindLambdaForTesting([&]() { loop.Run(/*inside=*/true); }),
        base::BindLambdaForTesting([&]() { quit_ = true; }));
    while (!quit_) {
      loop.Run(/*inside=*/false);
    }
  }
};

TEST_F(DragDropControllerTest, DragDropInSingleViewTest) {
  std::unique_ptr<views::Widget> widget = CreateFramelessWidget();
  DragTestView* drag_view = new DragTestView;
  AddViewToWidgetAndResize(widget.get(), drag_view);
  ui::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
                                     widget->GetNativeView());
  generator.PressLeftButton();

  int num_drags = 17;
  for (int i = 0; i < num_drags; ++i) {
    generator.MoveMouseBy(0, 1);
  }

  generator.ReleaseLeftButton();

  EXPECT_TRUE(drag_drop_controller_->drag_start_received_);
  EXPECT_EQ(num_drags - 1 - drag_view->VerticalDragThreshold(),
            drag_drop_controller_->num_drag_updates_);
  EXPECT_TRUE(drag_drop_controller_->drop_received_);
  EXPECT_EQ(u"I am being dragged", drag_drop_controller_->drag_string_);

  EXPECT_EQ(1, drag_view->num_drag_enters_);
  EXPECT_EQ(num_drags - 1 - drag_view->VerticalDragThreshold(),
            drag_view->num_drag_updates_);
  EXPECT_EQ(1, drag_view->num_drops_);
  EXPECT_EQ(0, drag_view->num_drag_exits_);
  EXPECT_TRUE(drag_view->drag_done_received_);
}

TEST_F(DragDropControllerTest, DragDropMouseReleasesWindowCapture) {
  std::unique_ptr<views::Widget> widget = CreateFramelessWidget();
  DragTestView* drag_view = new DragTestView;
  AddViewToWidgetAndResize(widget.get(), drag_view);
  aura::Window* window = widget->GetNativeView();

  EXPECT_FALSE(window->HasCapture());

  ui::test::EventGenerator generator(Shell::GetPrimaryRootWindow(), window);

  generator.PressLeftButton();

  // aura::Window does not explicitly take capture, so call this manually to
  // simulate dragging a view which does take capture.
  window->SetCapture();
  EXPECT_TRUE(window->HasCapture());

  int n = 0;
  auto loop_task = [&](bool inside) {
    generator.MoveMouseBy(0, 1);
    n++;

    if (n == 17)
      generator.ReleaseLeftButton();
  };
  RunWithClosure(base::BindLambdaForTesting(loop_task));

  EXPECT_TRUE(drag_view->drag_done_received_);
  EXPECT_FALSE(window->HasCapture());
}

TEST_F(DragDropControllerTest, DragDropWithZeroDragUpdates) {
  std::unique_ptr<views::Widget> widget = CreateFramelessWidget();
  DragTestView* drag_view = new DragTestView;
  AddViewToWidgetAndResize(widget.get(), drag_view);
  ui::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
                                     widget->GetNativeView());
  generator.PressLeftButton();

  int num_drags = drag_view->VerticalDragThreshold() + 1;
  for (int i = 0; i < num_drags; ++i) {
    generator.MoveMouseBy(0, 1);
  }

  generator.ReleaseLeftButton();

  EXPECT_TRUE(drag_drop_controller_->drag_start_received_);
  EXPECT_EQ(num_drags - 1 - drag_view->VerticalDragThreshold() + 1,
            drag_drop_controller_->num_drag_updates_);
  EXPECT_TRUE(drag_drop_controller_->drop_received_);

  EXPECT_EQ(1, drag_view->num_drag_enters_);
  EXPECT_EQ(num_drags - 1 - drag_view->VerticalDragThreshold() + 1,
            drag_view->num_drag_updates_);
  EXPECT_EQ(1, drag_view->num_drops_);
  EXPECT_EQ(0, drag_view->num_drag_exits_);
  EXPECT_TRUE(drag_view->drag_done_received_);
}

TEST_F(DragDropControllerTest, DragDropInMultipleViewsSingleWidgetTest) {
  std::unique_ptr<views::Widget> widget = CreateFramelessWidget();
  DragTestView* drag_view1 = new DragTestView;
  AddViewToWidgetAndResize(widget.get(), drag_view1);
  DragTestView* drag_view2 = new DragTestView;
  AddViewToWidgetAndResize(widget.get(), drag_view2);

  ui::test::EventGenerator generator(Shell::GetPrimaryRootWindow());
  generator.MoveMouseRelativeTo(widget->GetNativeView(),
                                drag_view1->bounds().CenterPoint());
  generator.PressLeftButton();

  int num_drags = drag_view1->width();
  for (int i = 0; i < num_drags; ++i) {
    generator.MoveMouseBy(1, 0);
  }

  generator.ReleaseLeftButton();

  EXPECT_TRUE(drag_drop_controller_->drag_start_received_);
  EXPECT_EQ(num_drags - 1 - drag_view1->HorizontalDragThreshold(),
            drag_drop_controller_->num_drag_updates_);
  EXPECT_TRUE(drag_drop_controller_->drop_received_);
  EXPECT_EQ(u"I am being dragged", drag_drop_controller_->drag_string_);

  EXPECT_EQ(1, drag_view1->num_drag_enters_);
  int num_expected_updates =
      drag_view1->bounds().width() - drag_view1->bounds().CenterPoint().x() - 2;
  EXPECT_EQ(num_expected_updates - drag_view1->HorizontalDragThreshold(),
            drag_view1->num_drag_updates_);
  EXPECT_EQ(0, drag_view1->num_drops_);
  EXPECT_EQ(1, drag_view1->num_drag_exits_);
  EXPECT_TRUE(drag_view1->drag_done_received_);

  EXPECT_EQ(1, drag_view2->num_drag_enters_);
  num_expected_updates = num_drags - num_expected_updates - 1;
  EXPECT_EQ(num_expected_updates, drag_view2->num_drag_updates_);
  EXPECT_EQ(1, drag_view2->num_drops_);
  EXPECT_EQ(0, drag_view2->num_drag_exits_);
  EXPECT_FALSE(drag_view2->drag_done_received_);
}

TEST_F(DragDropControllerTest, DragDropInMultipleViewsMultipleWidgetsTest) {
  std::unique_ptr<views::Widget> widget1 = CreateFramelessWidget();
  DragTestView* drag_view1 = new DragTestView;
  AddViewToWidgetAndResize(widget1.get(), drag_view1);
  std::unique_ptr<views::Widget> widget2 = CreateFramelessWidget();
  DragTestView* drag_view2 = new DragTestView;
  AddViewToWidgetAndResize(widget2.get(), drag_view2);
  gfx::Rect widget1_bounds = widget1->GetClientAreaBoundsInScreen();
  gfx::Rect widget2_bounds = widget2->GetClientAreaBoundsInScreen();
  widget2->SetBounds(gfx::Rect(widget1_bounds.width(), 0,
                               widget2_bounds.width(),
                               widget2_bounds.height()));

  ui::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
                                     widget1->GetNativeView());
  generator.PressLeftButton();

  int num_drags = drag_view1->width();
  for (int i = 0; i < num_drags; ++i) {
    generator.MoveMouseBy(1, 0);
  }

  generator.ReleaseLeftButton();

  EXPECT_TRUE(drag_drop_controller_->drag_start_received_);
  EXPECT_EQ(num_drags - 1 - drag_view1->HorizontalDragThreshold(),
            drag_drop_controller_->num_drag_updates_);
  EXPECT_TRUE(drag_drop_controller_->drop_received_);
  EXPECT_EQ(u"I am being dragged", drag_drop_controller_->drag_string_);

  EXPECT_EQ(1, drag_view1->num_drag_enters_);
  int num_expected_updates =
      drag_view1->bounds().width() - drag_view1->bounds().CenterPoint().x() - 2;
  EXPECT_EQ(num_expected_updates - drag_view1->HorizontalDragThreshold(),
            drag_view1->num_drag_updates_);
  EXPECT_EQ(0, drag_view1->num_drops_);
  EXPECT_EQ(1, drag_view1->num_drag_exits_);
  EXPECT_TRUE(drag_view1->drag_done_received_);

  EXPECT_EQ(1, drag_view2->num_drag_enters_);
  num_expected_updates = num_drags - num_expected_updates - 1;
  EXPECT_EQ(num_expected_updates, drag_view2->num_drag_updates_);
  EXPECT_EQ(1, drag_view2->num_drops_);
  EXPECT_EQ(0, drag_view2->num_drag_exits_);
  EXPECT_FALSE(drag_view2->drag_done_received_);
}

TEST_F(DragDropControllerTest, ViewRemovedWhileInDragDropTest) {
  std::unique_ptr<views::Widget> widget = CreateFramelessWidget();
  std::unique_ptr<DragTestView> drag_view(new DragTestView);
  AddViewToWidgetAndResize(widget.get(), drag_view.get());

  ui::test::EventGenerator generator(Shell::GetPrimaryRootWindow());
  generator.MoveMouseToCenterOf(widget->GetNativeView());
  generator.PressLeftButton();

  int num_drags_1 = 17;
  for (int i = 0; i < num_drags_1; ++i) {
    generator.MoveMouseBy(0, 1);
  }

  drag_view->parent()->RemoveChildView(drag_view.get());
  // View has been removed. We will not get any of the following drag updates.
  int num_drags_2 = 23;
  for (int i = 0; i < num_drags_2; ++i) {
    generator.MoveMouseBy(0, 1);
  }

  generator.ReleaseLeftButton();

  EXPECT_TRUE(drag_drop_controller_->drag_start_received_);
  EXPECT_EQ(num_drags_1 + num_drags_2 - 1 - drag_view->VerticalDragThreshold(),
            drag_drop_controller_->num_drag_updates_);
  EXPECT_TRUE(drag_drop_controller_->drop_received_);
  EXPECT_EQ(u"I am being dragged", drag_drop_controller_->drag_string_);

  EXPECT_EQ(1, drag_view->num_drag_enters_);
  EXPECT_EQ(num_drags_1 - 1 - drag_view->VerticalDragThreshold(),
            drag_view->num_drag_updates_);
  EXPECT_EQ(0, drag_view->num_drops_);
  EXPECT_EQ(0, drag_view->num_drag_exits_);
  EXPECT_TRUE(drag_view->drag_done_received_);
}

TEST_F(DragDropControllerTest, DragLeavesClipboardAloneTest) {
  ui::Clipboard* cb = ui::Clipboard::GetForCurrentThread();
  std::string clip_str("I am on the clipboard");
  {
    // We first copy some text to the clipboard.
    ui::ScopedClipboardWriter scw(ui::ClipboardBuffer::kCopyPaste);
    scw.WriteText(base::ASCIIToUTF16(clip_str));
  }
  EXPECT_TRUE(cb->IsFormatAvailable(ui::ClipboardFormatType::PlainTextType(),
                                    ui::ClipboardBuffer::kCopyPaste,
                                    /* data_dst = */ nullptr));

  std::unique_ptr<views::Widget> widget = CreateFramelessWidget();
  DragTestView* drag_view = new DragTestView;
  AddViewToWidgetAndResize(widget.get(), drag_view);

  ui::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
                                     widget->GetNativeView());
  generator.PressLeftButton();
  generator.MoveMouseBy(0, drag_view->VerticalDragThreshold() + 1);

  // Execute any scheduled draws to process deferred mouse events.
  base::RunLoop().RunUntilIdle();

  // Verify the clipboard contents haven't changed
  std::string result;
  EXPECT_TRUE(cb->IsFormatAvailable(ui::ClipboardFormatType::PlainTextType(),
                                    ui::ClipboardBuffer::kCopyPaste,
                                    /* data_dst = */ nullptr));
  cb->ReadAsciiText(ui::ClipboardBuffer::kCopyPaste, /* data_dst = */ nullptr,
                    &result);
  EXPECT_EQ(clip_str, result);
  // Destroy the clipboard here because ash doesn't delete it.
  // crbug.com/158150.
  ui::Clipboard::DestroyClipboardForCurrentThread();
}

// Cancelling followed by closing window should not cause use after free.
// This happens when closing a browser window while dragging.
// crbug.com/1282480
TEST_F(DragDropControllerTest, DragCanceledThenWindowDestroyedDuringDragDrop) {
  std::unique_ptr<views::Widget> widget = CreateFramelessWidget();
  DragTestView* drag_view = new DragTestView;
  AddViewToWidgetAndResize(widget.get(), drag_view);
  aura::Window* window = widget->GetNativeView();

  EventTargetTestDelegate delegate(window);
  aura::client::SetDragDropDelegate(window, &delegate);

  ui::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
                                     widget->GetNativeView());
  generator.PressLeftButton();

  int n = 0;
  bool dragged = false;
  auto loop_task = [&](bool inside) {
    generator.MoveMouseBy(0, 1);
    base::RunLoop().RunUntilIdle();
    n++;

    if (inside && window) {
      dragged = true;
      EXPECT_EQ(window, GetDragWindow());
      EXPECT_GT(n, drag_view->VerticalDragThreshold());
    }

    if (n == 18) {
      drag_drop_controller_->DragCancel();
      widget->CloseNow();
      EXPECT_FALSE(this->GetDragWindow());
      window = nullptr;
    }
  };
  RunWithClosure(base::BindLambdaForTesting(loop_task));
  EXPECT_TRUE(dragged);
  EXPECT_FALSE(GetDragWindow());
  generator.ReleaseLeftButton();

  EXPECT_TRUE(drag_drop_controller_->drag_start_received_);
  // Drag must have been canceled.
  EXPECT_TRUE(drag_drop_controller_->drag_canceled_);
  EXPECT_FALSE(drag_drop_controller_->drop_received_);

  EXPECT_EQ(EventTargetTestDelegate::State::kDragExitInvoked, delegate.state());
}

TEST_F(DragDropControllerTest, SyntheticEventsDuringDragDrop) {
  std::unique_ptr<views::Widget> widget = CreateFramelessWidget();
  DragTestView* drag_view = new DragTestView;
  AddViewToWidgetAndResize(widget.get(), drag_view);
  ui::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
                                     widget->GetNativeView());
  generator.PressLeftButton();

  int num_drags = 17;
  for (int i = 0; i < num_drags; ++i) {
    generator.MoveMouseBy(0, 1);

    // We send a unexpected mouse move event. Note that we cannot use
    // EventGenerator since it implicitly turns these into mouse drag events.
    // The DragDropController should simply ignore these events.
    gfx::Point mouse_move_location = drag_view->bounds().CenterPoint();
    ui::MouseEvent mouse_move(ui::EventType::kMouseMoved, mouse_move_location,
                              mouse_move_location, ui::EventTimeForNow(), 0, 0);
    ui::EventDispatchDetails details = Shell::GetPrimaryRootWindow()
                                           ->GetHost()
                                           ->GetEventSink()
                                           ->OnEventFromSource(&mouse_move);
    ASSERT_FALSE(details.dispatcher_destroyed);
  }

  generator.ReleaseLeftButton();

  EXPECT_TRUE(drag_drop_controller_->drag_start_received_);
  EXPECT_EQ(num_drags - 1 - drag_view->VerticalDragThreshold(),
            drag_drop_controller_->num_drag_updates_);
  EXPECT_TRUE(drag_drop_controller_->drop_received_);
  EXPECT_EQ(u"I am being dragged", drag_drop_controller_->drag_string_);

  EXPECT_EQ(1, drag_view->num_drag_enters_);
  EXPECT_EQ(num_drags - 1 - drag_view->VerticalDragThreshold(),
            drag_view->num_drag_updates_);
  EXPECT_EQ(1, drag_view->num_drops_);
  EXPECT_EQ(0, drag_view->num_drag_exits_);
  EXPECT_TRUE(drag_view->drag_done_received_);
}

TEST_F(DragDropControllerTest, PressingEscapeCancelsDragDrop) {
  std::unique_ptr<views::Widget> widget = CreateFramelessWidget();
  DragTestView* drag_view = new DragTestView;
  AddViewToWidgetAndResize(widget.get(), drag_view);
  ui::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
                                     widget->GetNativeView());
  generator.PressLeftButton();

  int num_drags = 17;
  for (int i = 0; i < num_drags; ++i) {
    generator.MoveMouseBy(0, 1);
  }

  generator.PressKey(ui::VKEY_ESCAPE, 0);

  EXPECT_TRUE(drag_drop_controller_->drag_start_received_);
  EXPECT_EQ(num_drags - 1 - drag_view->VerticalDragThreshold(),
            drag_drop_controller_->num_drag_updates_);
  EXPECT_FALSE(drag_drop_controller_->drop_received_);
  EXPECT_TRUE(drag_drop_controller_->drag_canceled_);
  EXPECT_EQ(u"I am being dragged", drag_drop_controller_->drag_string_);

  EXPECT_EQ(1, drag_view->num_drag_enters_);
  EXPECT_EQ(num_drags - 1 - drag_view->VerticalDragThreshold(),
            drag_view->num_drag_updates_);
  EXPECT_EQ(0, drag_view->num_drops_);
  EXPECT_EQ(1, drag_view->num_drag_exits_);
  EXPECT_TRUE(drag_view->drag_done_received_);
}

TEST_F(DragDropControllerTest, CaptureLostDoesNotCancelsDragDrop) {
  std::unique_ptr<views::Widget> widget = CreateFramelessWidget();
  DragTestView* drag_view = new DragTestView;
  AddViewToWidgetAndResize(widget.get(), drag_view);
  ui::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
                                     widget->GetNativeView());
  generator.PressLeftButton();

  int num_drags = 17;
  for (int i = 0; i < num_drags; ++i) {
    generator.MoveMouseBy(0, 1);
  }

  aura::client::GetCaptureClient(widget->GetNativeView()->GetRootWindow())
      ->SetCapture(nullptr);

  EXPECT_TRUE(drag_drop_controller_->drag_start_received_);
  EXPECT_EQ(num_drags - 1 - drag_view->VerticalDragThreshold(),
            drag_drop_controller_->num_drag_updates_);
  EXPECT_FALSE(drag_drop_controller_->drop_received_);
  EXPECT_FALSE(drag_drop_controller_->drag_canceled_);
  EXPECT_EQ(u"I am being dragged", drag_drop_controller_->drag_string_);

  EXPECT_EQ(1, drag_view->num_drag_enters_);
  EXPECT_EQ(num_drags - 1 - drag_view->VerticalDragThreshold(),
            drag_view->num_drag_updates_);
  EXPECT_EQ(0, drag_view->num_drops_);
  EXPECT_EQ(0, drag_view->num_drag_exits_);
  EXPECT_TRUE(drag_view->drag_done_received_);
}

TEST_F(DragDropControllerTest, TouchDragDropInMultipleWindows) {
  std::unique_ptr<views::Widget> widget1 = CreateFramelessWidget();
  DragTestView* drag_view1 = new DragTestView;
  AddViewToWidgetAndResize(widget1.get(), drag_view1);
  std::unique_ptr<views::Widget> widget2 = CreateFramelessWidget();
  DragTestView* drag_view2 = new DragTestView;
  AddViewToWidgetAndResize(widget2.get(), drag_view2);
  gfx::Rect widget1_bounds = widget1->GetClientAreaBoundsInScreen();
  gfx::Rect widget2_bounds = widget2->GetClientAreaBoundsInScreen();
  widget2->SetBounds(gfx::Rect(widget1_bounds.width(), 0,
                               widget2_bounds.width(),
                               widget2_bounds.height()));

  ui::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
                                     widget1->GetNativeView());
  generator.PressTouch();
  gfx::Point point = gfx::Rect(drag_view1->bounds()).CenterPoint();
  DispatchGesture(ui::EventType::kGestureLongPress, point);
  gfx::Point gesture_location = point;
  int num_drags = drag_view1->width();
  for (int i = 0; i < num_drags; ++i) {
    gesture_location.Offset(1, 0);
    DispatchGesture(ui::EventType::kGestureScrollUpdate, gesture_location);
  }

  DispatchGesture(ui::EventType::kGestureScrollEnd, gesture_location);

  EXPECT_TRUE(drag_drop_controller_->drag_start_received_);
  EXPECT_EQ(num_drags, drag_drop_controller_->num_drag_updates_);
  EXPECT_TRUE(drag_drop_controller_->drop_received_);
  EXPECT_EQ(u"I am being dragged", drag_drop_controller_->drag_string_);

  EXPECT_EQ(1, drag_view1->num_drag_enters_);
  int num_expected_updates =
      drag_view1->bounds().width() - drag_view1->bounds().CenterPoint().x() - 1;
  EXPECT_EQ(num_expected_updates, drag_view1->num_drag_updates_);
  EXPECT_EQ(0, drag_view1->num_drops_);
  EXPECT_EQ(1, drag_view1->num_drag_exits_);
  EXPECT_TRUE(drag_view1->drag_done_received_);

  EXPECT_EQ(1, drag_view2->num_drag_enters_);
  num_expected_updates = num_drags - num_expected_updates;
  EXPECT_EQ(num_expected_updates, drag_view2->num_drag_updates_);
  EXPECT_EQ(1, drag_view2->num_drops_);
  EXPECT_EQ(0, drag_view2->num_drag_exits_);
  EXPECT_FALSE(drag_view2->drag_done_received_);
}

TEST_F(DragDropControllerTest, DragDropWithChangingIcon) {
  std::unique_ptr<views::Widget> widget = CreateFramelessWidget();
  DragTestView* drag_view1 = new DragTestView;
  AddViewToWidgetAndResize(widget.get(), drag_view1);
  DragTestView* drag_view2 = new DragTestView;
  AddViewToWidgetAndResize(widget.get(), drag_view2);

  ui::test::EventGenerator generator(Shell::GetPrimaryRootWindow());
  generator.MoveMouseRelativeTo(widget->GetNativeView(),
                                drag_view1->bounds().CenterPoint());
  generator.PressLeftButton();

  int num_drags = drag_view1->width();
  int icon_replacements = 0;
  for (int i = 0; i < num_drags; ++i) {
    if (drag_drop_controller_->IsDragDropInProgress()) {
      if (!GetDragSourceWindow())
        SetDragSourceWindow(widget->GetNativeWindow());

      gfx::ImageSkia new_icon;
      new_icon.AddRepresentation(gfx::ImageSkiaRep(gfx::Size(10, 10), 1.0f));

      EXPECT_FALSE(GetDragImage().BackedBySameObjectAs(new_icon));
      drag_drop_controller_->SetDragImage(new_icon, gfx::Vector2d());
      EXPECT_TRUE(GetDragImage().BackedBySameObjectAs(new_icon));
      icon_replacements++;
    }

    generator.MoveMouseBy(1, 0);
  }

  generator.ReleaseLeftButton();

  EXPECT_GT(icon_replacements, 0);

  EXPECT_TRUE(drag_drop_controller_->drag_start_received_);
  EXPECT_EQ(num_drags - 1 - drag_view1->HorizontalDragThreshold(),
            drag_drop_controller_->num_drag_updates_);
  EXPECT_TRUE(drag_drop_controller_->drop_received_);
  EXPECT_EQ(u"I am being dragged", drag_drop_controller_->drag_string_);

  EXPECT_EQ(1, drag_view1->num_drag_enters_);
  int num_expected_updates =
      drag_view1->bounds().width() - drag_view1->bounds().CenterPoint().x() - 2;
  EXPECT_EQ(num_expected_updates - drag_view1->HorizontalDragThreshold(),
            drag_view1->num_drag_updates_);
  EXPECT_EQ(0, drag_view1->num_drops_);
  EXPECT_EQ(1, drag_view1->num_drag_exits_);
  EXPECT_TRUE(drag_view1->drag_done_received_);

  EXPECT_EQ(1, drag_view2->num_drag_enters_);
  num_expected_updates = num_drags - num_expected_updates - 1;
  EXPECT_EQ(num_expected_updates, drag_view2->num_drag_updates_);
  EXPECT_EQ(1, drag_view2->num_drops_);
  EXPECT_EQ(0, drag_view2->num_drag_exits_);
  EXPECT_FALSE(drag_view2->drag_done_received_);
}

namespace {

class DragImageWindowObserver : public aura::WindowObserver {
 public:
  void OnWindowDestroying(aura::Window* window) override {
    window_location_on_destroying_ = window->GetBoundsInScreen().origin();
  }

  gfx::Point window_location_on_destroying() const {
    return window_location_on_destroying_;
  }

 public:
  gfx::Point window_location_on_destroying_;
};

}  // namespace

// Verifies the drag image moves back to the position where drag is started
// across displays when drag is cancelled.
TEST_F(DragDropControllerTest, DragCancelAcrossDisplays) {
  UpdateDisplay("500x400,500x400");
  aura::Window::Windows root_windows = Shell::Get()->GetAllRootWindows();
  for (aura::Window::Windows::iterator iter = root_windows.begin();
       iter != root_windows.end(); ++iter) {
    aura::client::SetDragDropClient(*iter, drag_drop_controller_.get());
  }

  {
    auto data = CreateDragData(/*with_image=*/true);
    std::unique_ptr<views::Widget> widget = CreateFramelessWidget();
    aura::Window* window = widget->GetNativeWindow();
    drag_drop_controller_->StartDragAndDrop(
        std::move(data), window->GetRootWindow(), window, gfx::Point(5, 5),
        ui::DragDropTypes::DRAG_MOVE, ui::mojom::DragEventSource::kMouse);

    DragImageWindowObserver observer;
    ASSERT_TRUE(GetDragImageWindow());
    GetDragImageWindow()->AddObserver(&observer);

    {
      ui::MouseEvent e(ui::EventType::kMouseDragged, gfx::Point(200, 0),
                       gfx::Point(200, 0), ui::EventTimeForNow(), ui::EF_NONE,
                       ui::EF_NONE);
      drag_drop_controller_->DragUpdate(window, e);
    }
    {
      ui::MouseEvent e(ui::EventType::kMouseDragged, gfx::Point(600, 0),
                       gfx::Point(600, 0), ui::EventTimeForNow(), ui::EF_NONE,
                       ui::EF_NONE);
      drag_drop_controller_->DragUpdate(window, e);
    }

    drag_drop_controller_->DragCancel();
    CompleteCancelAnimation();
    // Make sure all pending tasks complete to finish cancellation.
    base::RunLoop().RunUntilIdle();

    EXPECT_EQ("5,5", observer.window_location_on_destroying().ToString());
  }

  {
    auto data = CreateDragData(/*with_image=*/true);
    std::unique_ptr<views::Widget> widget = CreateFramelessWidget();
    aura::Window* window = widget->GetNativeWindow();
    drag_drop_controller_->StartDragAndDrop(
        std::move(data), window->GetRootWindow(), window, gfx::Point(405, 405),
        ui::DragDropTypes::DRAG_MOVE, ui::mojom::DragEventSource::kMouse);
    DragImageWindowObserver observer;
    ASSERT_TRUE(GetDragImageWindow());
    GetDragImageWindow()->AddObserver(&observer);

    {
      ui::MouseEvent e(ui::EventType::kMouseDragged, gfx::Point(600, 0),
                       gfx::Point(600, 0), ui::EventTimeForNow(), ui::EF_NONE,
                       ui::EF_NONE);
      drag_drop_controller_->DragUpdate(window, e);
    }
    {
      ui::MouseEvent e(ui::EventType::kMouseDragged, gfx::Point(200, 0),
                       gfx::Point(200, 0), ui::EventTimeForNow(), ui::EF_NONE,
                       ui::EF_NONE);
      drag_drop_controller_->DragUpdate(window, e);
    }

    drag_drop_controller_->DragCancel();
    CompleteCancelAnimation();
    // Make sure all pending tasks complete to finish cancellation.
    base::RunLoop().RunUntilIdle();

    EXPECT_EQ("405,405", observer.window_location_on_destroying().ToString());
  }
  for (aura::Window::Windows::iterator iter = root_windows.begin();
       iter != root_windows.end(); ++iter) {
    aura::client::SetDragDropClient(*iter, NULL);
  }
}

// Verifies that a drag is aborted if a display is disconnected during the drag.
TEST_F(DragDropControllerTest, DragCancelOnDisplayDisconnect) {
  UpdateDisplay("500x400,500x400");
  for (aura::Window* root : Shell::Get()->GetAllRootWindows()) {
    aura::client::SetDragDropClient(root, drag_drop_controller_.get());
  }

  auto data = CreateDragData(/*with_image=*/false);
  std::unique_ptr<views::Widget> widget = CreateFramelessWidget();
  aura::Window* window = widget->GetNativeWindow();
  drag_drop_controller_->StartDragAndDrop(
      std::move(data), window->GetRootWindow(), window, gfx::Point(5, 5),
      ui::DragDropTypes::DRAG_MOVE, ui::mojom::DragEventSource::kMouse);

  // Start dragging.
  ui::MouseEvent e1(ui::EventType::kMouseDragged, gfx::Point(200, 0),
                    gfx::Point(200, 0), ui::EventTimeForNow(), ui::EF_NONE,
                    ui::EF_NONE);
  drag_drop_controller_->DragUpdate(window, e1);
  EXPECT_TRUE(drag_drop_controller_->drag_start_received_);
  EXPECT_TRUE(drag_drop_controller_->IsDragDropInProgress());

  // Drag onto the secondary display.
  ui::MouseEvent e2(ui::EventType::kMouseDragged, gfx::Point(600, 0),
                    gfx::Point(600, 0), ui::EventTimeForNow(), ui::EF_NONE,
                    ui::EF_NONE);
  drag_drop_controller_->DragUpdate(window, e2);
  EXPECT_TRUE(drag_drop_controller_->IsDragDropInProgress());

  // Disconnect the secondary display.
  UpdateDisplay("800x600");

  // The drag is canceled.
  EXPECT_TRUE(drag_drop_controller_->drag_canceled_);
  EXPECT_FALSE(drag_drop_controller_->IsDragDropInProgress());
}

TEST_F(DragDropControllerTest, TouchDragDropCompletesOnFling) {
  ui::GestureConfiguration::GetInstance()
      ->set_max_touch_move_in_pixels_for_click(1);
  std::unique_ptr<views::Widget> widget = CreateFramelessWidget();
  DragTestView* drag_view = new DragTestView;
  AddViewToWidgetAndResize(widget.get(), drag_view);
  ui::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
                                     widget->GetNativeView());

  gfx::Point start = gfx::Rect(drag_view->bounds()).CenterPoint();
  gfx::Point mid = start + gfx::Vector2d(drag_view->bounds().width() / 6, 0);
  gfx::Point end = start + gfx::Vector2d(drag_view->bounds().width() / 3, 0);

  base::TimeTicks timestamp = ui::EventTimeForNow();
  ui::TouchEvent press(ui::EventType::kTouchPressed, start, timestamp,
                       ui::PointerDetails(ui::EventPointerType::kTouch, 0));
  generator.Dispatch(&press);

  DispatchGesture(ui::EventType::kGestureLongPress, start);
  timestamp += base::Milliseconds(10);
  ui::TouchEvent move1(ui::EventType::kTouchMoved, mid, timestamp,
                       ui::PointerDetails(ui::EventPointerType::kTouch, 0));
  generator.Dispatch(&move1);
  // Doing two moves instead of one will guarantee to generate a fling at the
  // end.
  timestamp += base::Milliseconds(10);
  ui::TouchEvent move2(ui::EventType::kTouchMoved, end, timestamp,
                       ui::PointerDetails(ui::EventPointerType::kTouch, 0));
  generator.Dispatch(&move2);
  ui::TouchEvent release(ui::EventType::kTouchReleased, end, timestamp,
                         ui::PointerDetails(ui::EventPointerType::kTouch, 0));
  generator.Dispatch(&release);

  EXPECT_TRUE(drag_drop_controller_->drag_start_received_);
  EXPECT_FALSE(drag_drop_controller_->drag_canceled_);
  EXPECT_EQ(2, drag_drop_controller_->num_drag_updates_);
  EXPECT_TRUE(drag_drop_controller_->drop_received_);
  EXPECT_EQ(u"I am being dragged", drag_drop_controller_->drag_string_);
  EXPECT_EQ(1, drag_view->num_drag_enters_);
  EXPECT_EQ(2, drag_view->num_drag_updates_);
  EXPECT_EQ(1, drag_view->num_drops_);
  EXPECT_EQ(0, drag_view->num_drag_exits_);
  EXPECT_TRUE(drag_view->drag_done_received_);
}

TEST_F(DragDropControllerTest, DragObserverEvents) {
  testing::StrictMock<MockDragDropObserver> observer(
      drag_drop_controller_.get());

  {
    auto data = CreateDragData(/*with_image=*/false);
    ui::OSExchangeData* data_ptr = data.get();

    std::unique_ptr<views::Widget> widget = CreateFramelessWidget();
    aura::Window* window = widget->GetNativeWindow();

    EXPECT_CALL(observer, OnDragStarted);
    drag_drop_controller_->StartDragAndDrop(
        std::move(data), window->GetRootWindow(), window, gfx::Point(5, 5),
        ui::DragDropTypes::DRAG_MOVE, ui::mojom::DragEventSource::kMouse);
    testing::Mock::VerifyAndClearExpectations(&observer);

    ui::MouseEvent e(ui::EventType::kMouseDragged, gfx::Point(200, 0),
                     gfx::Point(200, 0), ui::EventTimeForNow(), ui::EF_NONE,
                     ui::EF_NONE);

    {
      testing::InSequence sequence;
      EXPECT_CALL(observer, OnDragUpdated)
          .WillOnce(testing::Invoke([&](const ui::DropTargetEvent& event) {
            gfx::Point root_location_in_screen = event.root_location();
            ::wm::ConvertPointToScreen(
                static_cast<aura::Window*>(event.target())->GetRootWindow(),
                &root_location_in_screen);
            EXPECT_EQ(gfx::Point(200, 0), root_location_in_screen);
            EXPECT_EQ(&event.data(), data_ptr);
          }));
      EXPECT_CALL(observer, OnDragCompleted);
      EXPECT_CALL(observer, OnDropCompleted);
    }

    drag_drop_controller_->Drop(window, e);
    testing::Mock::VerifyAndClearExpectations(&observer);
  }

  drag_drop_controller_->RemoveObserver(&observer);
}

TEST_F(DragDropControllerTest, SetEnabled) {
  TestObserver observer;
  drag_drop_controller_->AddObserver(&observer);

  // Data for the drag.
  auto data = CreateDragData(/*with_image=*/false);
  std::unique_ptr<views::Widget> widget = CreateFramelessWidget();
  aura::Window* window = widget->GetNativeWindow();

  // Cannot start a drag when the controller is disabled.
  drag_drop_controller_->set_enabled(false);
  drag_drop_controller_->StartDragAndDrop(
      std::move(data), window->GetRootWindow(), window, gfx::Point(5, 5),
      ui::DragDropTypes::DRAG_MOVE, ui::mojom::DragEventSource::kMouse);
  EXPECT_EQ(TestObserver::State::kNotInvoked, observer.state());

  drag_drop_controller_->RemoveObserver(&observer);
}

TEST_F(DragDropControllerTest, EventTarget) {
  std::unique_ptr<aura::Window> window(CreateTestWindowInShellWithDelegate(
      aura::test::TestWindowDelegate::CreateSelfDestroyingDelegate(), -1,
      gfx::Rect(0, 0, 100, 100)));
  EventTargetTestDelegate delegate(window.get());
  aura::client::SetDragDropDelegate(window.get(), &delegate);

  // Posted task will be run when the inner loop runs in StartDragAndDrop.
  ui::test::EventGenerator generator(window->GetRootWindow(), window.get());
  generator.PressLeftButton();
  // For drag enter
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&ui::test::EventGenerator::MoveMouseBy,
                                base::Unretained(&generator), 0, 1));
  // For drag update
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&ui::test::EventGenerator::MoveMouseBy,
                                base::Unretained(&generator), 0, 1));
  // For perform drop
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&ui::test::EventGenerator::ReleaseLeftButton,
                                base::Unretained(&generator)));

  drag_drop_controller_->set_should_block_during_drag_drop(true);
  auto data = CreateDragData(/*with_image=*/false);
  drag_drop_controller_->StartDragAndDrop(
      std::move(data), window->GetRootWindow(), window.get(), gfx::Point(5, 5),
      ui::DragDropTypes::DRAG_MOVE, ui::mojom::DragEventSource::kMouse);

  EXPECT_EQ(EventTargetTestDelegate::State::kPerformDropInvoked,
            delegate.state());
  base::RunLoop().RunUntilIdle();
}

// Verifies that a tab drag changes the drag operation to a move.
TEST_F(DragDropControllerTest, DragTabChangesDragOperationToMove) {
  EXPECT_CALL(*mock_shell_delegate(), IsTabDrag(_))
      .Times(1)
      .WillOnce(Return(true));
  std::unique_ptr<aura::Window> new_window = CreateToplevelTestWindow();
  EXPECT_CALL(*mock_new_window_delegate(), NewWindowForDetachingTab(_, _, _))
      .Times(1)
      .WillOnce(RunOnceCallback<2>(new_window.get()));

  std::unique_ptr<views::Widget> widget = CreateFramelessWidget();
  aura::Window* window = widget->GetNativeWindow();

  // Posted task will be run when the inner loop runs in StartDragAndDrop.
  ui::test::EventGenerator generator(window->GetRootWindow(), window);
  generator.PressLeftButton();
  // For drag enter.
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&ui::test::EventGenerator::MoveMouseBy,
                                base::Unretained(&generator), 0, 1));
  // For perform drop.
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&ui::test::EventGenerator::ReleaseLeftButton,
                                base::Unretained(&generator)));

  drag_drop_controller_->set_should_block_during_drag_drop(true);
  DragOperation operation = drag_drop_controller_->StartDragAndDrop(
      std::make_unique<ui::OSExchangeData>(), window->GetRootWindow(), window,
      gfx::Point(5, 5), ui::DragDropTypes::DRAG_NONE,
      ui::mojom::DragEventSource::kMouse);

  EXPECT_EQ(operation, DragOperation::kMove);
}

// Verifies that a tab drag does not crash (UAF) on source window destruction.
TEST_F(DragDropControllerTest, DragTabDoesNotCrashOnSourceWindowDestruction) {
  EXPECT_CALL(*mock_shell_delegate(), IsTabDrag(_))
      .Times(1)
      .WillOnce(Return(true));

  std::unique_ptr<views::Widget> widget = CreateFramelessWidget();
  aura::Window* window = widget->GetNativeWindow();

  // Posted task will be run when the inner loop runs in StartDragAndDrop.
  ui::test::EventGenerator generator(window->GetRootWindow(), window);
  generator.PressLeftButton();

  int step = 0;
  drag_drop_controller_->SetLoopClosureForTesting(
      base::BindLambdaForTesting([&]() {
        switch (step++) {
          case 0:
            // For drag enter.
            generator.MoveMouseBy(0, 1);
            break;
          case 1:
            // Forces a |TabDragDropDelegate::source_window_| destruction.
            widget.reset();
            break;
          case 2:
            // For perform more drag and drop.
            generator.ReleaseLeftButton();
            break;
          default:
            NOTREACHED();
        }
      }),
      base::DoNothing());

  DragOperation operation = drag_drop_controller_->StartDragAndDrop(
      std::make_unique<ui::OSExchangeData>(), window->GetRootWindow(), window,
      gfx::Point(5, 5), ui::DragDropTypes::DRAG_NONE,
      ui::mojom::DragEventSource::kMouse);

  EXPECT_EQ(step, 3);
  EXPECT_EQ(operation, DragOperation::kNone);
}

TEST_F(DragDropControllerTest, ToplevelWindowDragDelegate) {
  std::unique_ptr<aura::Window> window(CreateTestWindowInShellWithDelegate(
      aura::test::TestWindowDelegate::CreateSelfDestroyingDelegate(), -1,
      gfx::Rect(0, 0, 100, 100)));

  // Emulate a full drag and drop flow and verify that toplevel window drag
  // delegate gets notified about the events as expected.
  {
    TestToplevelWindowDragDelegate delegate;
    drag_drop_controller_->set_toplevel_window_drag_delegate(&delegate);

    ui::test::EventGenerator generator(window->GetRootWindow(), window.get());
    generator.PressLeftButton();

    auto data(std::make_unique<ui::OSExchangeData>());
    drag_drop_controller_->StartDragAndDrop(
        std::move(data), window->GetRootWindow(), window.get(),
        gfx::Point(5, 5), ui::DragDropTypes::DRAG_MOVE,
        ui::mojom::DragEventSource::kMouse);

    EXPECT_EQ(TestToplevelWindowDragDelegate::State::kDragStartedInvoked,
              delegate.state());
    EXPECT_EQ(ui::mojom::DragEventSource::kMouse, delegate.source());
    EXPECT_TRUE(delegate.current_location().has_value());
    EXPECT_EQ(gfx::PointF(5, 5), *delegate.current_location());
    EXPECT_EQ(0, delegate.events_forwarded());

    generator.MoveMouseBy(1, 1);
    generator.MoveMouseBy(1, 1);
    generator.MoveMouseBy(1, 1);
    generator.MoveMouseBy(1, 1);
    generator.ReleaseLeftButton();

    EXPECT_EQ(TestToplevelWindowDragDelegate::State::kDragDroppedInvoked,
              delegate.state());
    EXPECT_TRUE(delegate.current_location().has_value());
    EXPECT_EQ(gfx::PointF(54, 54), *delegate.current_location());
    EXPECT_EQ(5, delegate.events_forwarded());
  }

  // Emulate a drag session cancellation and verify the toplevel window drag
  // delegate gets notified about the events as expected.
  {
    TestToplevelWindowDragDelegate delegate;
    drag_drop_controller_->set_toplevel_window_drag_delegate(&delegate);

    ui::test::EventGenerator generator(window->GetRootWindow(), window.get());
    generator.PressLeftButton();

    auto data(std::make_unique<ui::OSExchangeData>());
    drag_drop_controller_->StartDragAndDrop(
        std::move(data), window->GetRootWindow(), window.get(),
        gfx::Point(5, 5), ui::DragDropTypes::DRAG_MOVE,
        ui::mojom::DragEventSource::kMouse);

    EXPECT_EQ(TestToplevelWindowDragDelegate::State::kDragStartedInvoked,
              delegate.state());
    EXPECT_EQ(ui::mojom::DragEventSource::kMouse, delegate.source());
    EXPECT_TRUE(delegate.current_location().has_value());
    EXPECT_EQ(gfx::PointF(5, 5), *delegate.current_location());
    EXPECT_EQ(0, delegate.events_forwarded());

    generator.MoveMouseBy(1, 1);
    generator.MoveMouseBy(1, 1);
    generator.PressKey(ui::VKEY_ESCAPE, 0);

    EXPECT_EQ(TestToplevelWindowDragDelegate::State::kDragCancelledInvoked,
              delegate.state());
    EXPECT_TRUE(delegate.current_location().has_value());
    EXPECT_EQ(gfx::PointF(52, 52), *delegate.current_location());
    EXPECT_EQ(2, delegate.events_forwarded());
  }

  // Regression test for https://crbug.com/1280128.
  // With 2 side-by-side displays, ensures that, when ext-dragging a toplevel
  // window from the rightmost display, entering in the leftmost display results
  // in correct mouse (drag update) events being sent over to toplevel window
  // drag delegate, i.e: negative 'x' coordinates.
  {
    UpdateDisplay("800x600,800x600");
    aura::Window::Windows root_windows = Shell::GetAllRootWindows();
    ASSERT_EQ(2u, root_windows.size());

    aura::client::SetDragDropClient(root_windows[0],
                                    drag_drop_controller_.get());
    aura::client::SetDragDropClient(root_windows[1],
                                    drag_drop_controller_.get());

    const gfx::Rect bounds_within_root1(0, 0, 800, 600);
    const gfx::Rect bounds_within_root2(800, 0, 800, 600);
    std::unique_ptr<aura::Window> window1 =
        CreateTestWindow(bounds_within_root1);
    std::unique_ptr<aura::Window> window2 =
        CreateTestWindow(bounds_within_root2);
    ASSERT_EQ(root_windows[0], window1->GetRootWindow());
    ASSERT_EQ(root_windows[1], window2->GetRootWindow());

    TestToplevelWindowDragDelegate delegate;
    drag_drop_controller_->set_toplevel_window_drag_delegate(&delegate);

    // Press and hold left mouse button at (0,0) in the rightmost display.
    ui::test::EventGenerator generator(window2->GetRootWindow(), {0, 0});
    generator.PressLeftButton();

    auto data(std::make_unique<ui::OSExchangeData>());
    drag_drop_controller_->StartDragAndDrop(
        std::move(data), window2->GetRootWindow(), window2.get(),
        gfx::Point(5, 5), ui::DragDropTypes::DRAG_MOVE,
        ui::mojom::DragEventSource::kMouse);

    EXPECT_EQ(TestToplevelWindowDragDelegate::State::kDragStartedInvoked,
              delegate.state());
    EXPECT_EQ(ui::mojom::DragEventSource::kMouse, delegate.source());
    EXPECT_TRUE(delegate.current_location().has_value());
    EXPECT_EQ(gfx::PointF(5, 5), *delegate.current_location());
    EXPECT_EQ(0, delegate.events_forwarded());

    // Drag to (790,0) in the root window corresponding to the leftmost display,
    // and ensure that the latest drag event received by toplevel window drag
    // delegate targets (-10, 0) location.
    generator.SetTargetWindow(root_windows[0]);
    generator.MoveMouseTo(gfx::Point(790, 0));

    EXPECT_TRUE(delegate.current_location().has_value());
    EXPECT_EQ(gfx::PointF(-10, 0), *delegate.current_location());
    EXPECT_EQ(1, delegate.events_forwarded());

    generator.ReleaseLeftButton();
    EXPECT_EQ(TestToplevelWindowDragDelegate::State::kDragDroppedInvoked,
              delegate.state());
    EXPECT_EQ(2, delegate.events_forwarded());
  }
}

TEST_F(DragDropControllerTest, ToplevelWindowDragDelegateWithTouch) {
  std::unique_ptr<aura::Window> window(CreateTestWindowInShellWithDelegate(
      aura::test::TestWindowDelegate::CreateSelfDestroyingDelegate(), -1,
      gfx::Rect(0, 0, 100, 100)));

  // Emulate a full drag and drop flow and verify that toplevel window drag
  // delegate gets notified about the events as expected.
  TestToplevelWindowDragDelegate delegate;
  drag_drop_controller_->set_toplevel_window_drag_delegate(&delegate);

  auto data(std::make_unique<ui::OSExchangeData>());
  drag_drop_controller_->StartDragAndDrop(
      std::move(data), window->GetRootWindow(), window.get(), gfx::Point(5, 5),
      ui::DragDropTypes::DRAG_MOVE, ui::mojom::DragEventSource::kTouch);

  EXPECT_EQ(TestToplevelWindowDragDelegate::State::kDragStartedInvoked,
            delegate.state());
  EXPECT_EQ(ui::mojom::DragEventSource::kTouch, delegate.source());
  EXPECT_TRUE(delegate.current_location().has_value());
  EXPECT_EQ(gfx::PointF(5, 5), *delegate.current_location());
  EXPECT_EQ(0, delegate.events_forwarded());

  drag_drop_controller_->DragCancel();
}

TEST_F(DragDropControllerTest, ToplevelWindowDragDelegateWithTouch2) {
  std::unique_ptr<aura::Window> window(CreateTestWindowInShellWithDelegate(
      aura::test::TestWindowDelegate::CreateSelfDestroyingDelegate(), -1,
      gfx::Rect(0, 0, 100, 100)));

  // Emulate a full drag and drop flow with touch and verify that toplevel
  // window drag delegate gets notified about the events as expected.
  TestToplevelWindowDragDelegate delegate;
  drag_drop_controller_->set_toplevel_window_drag_delegate(&delegate);

  ui::test::EventGenerator generator(window->GetRootWindow(), window.get());
  generator.PressTouch();
  auto point = gfx::Point(5, 5);

  auto data(std::make_unique<ui::OSExchangeData>());
  drag_drop_controller_->StartDragAndDrop(
      std::move(data), window->GetRootWindow(), window.get(), point,
      ui::DragDropTypes::DRAG_MOVE, ui::mojom::DragEventSource::kTouch);

  EXPECT_EQ(TestToplevelWindowDragDelegate::State::kDragStartedInvoked,
            delegate.state());
  EXPECT_EQ(ui::mojom::DragEventSource::kTouch, delegate.source());
  EXPECT_TRUE(delegate.current_location().has_value());
  EXPECT_EQ(gfx::PointF(point), *delegate.current_location());
  EXPECT_EQ(0, delegate.events_forwarded());

  gfx::Point gesture_location = point;
  int num_drags = 5;
  for (int i = 0; i < num_drags; ++i) {
    gesture_location.Offset(1, 1);
    DispatchGesture(ui::EventType::kGestureScrollUpdate, gesture_location);
    EXPECT_EQ(i + 1, delegate.events_forwarded());
  }

  DispatchGesture(ui::EventType::kGestureScrollEnd, gesture_location);
  EXPECT_EQ(TestToplevelWindowDragDelegate::State::kDragDroppedInvoked,
            delegate.state());
  EXPECT_TRUE(delegate.current_location().has_value());
  EXPECT_EQ(gfx::PointF(10, 10), *delegate.current_location());
  EXPECT_EQ(6, delegate.events_forwarded());
}

TEST_F(DragDropControllerTest, DragWithChromeTabDelegateTakesCapture) {
  EXPECT_CALL(*mock_shell_delegate(), IsTabDrag(_))
      .Times(1)
      .WillOnce(Return(true));

  std::unique_ptr<aura::Window> window(CreateTestWindowInShellWithDelegate(
      aura::test::TestWindowDelegate::CreateSelfDestroyingDelegate(), -1,
      gfx::Rect(0, 0, 100, 100)));

  auto data = CreateDragData(/*with_image=*/true);

  drag_drop_controller_->StartDragAndDrop(
      std::move(data), window->GetRootWindow(), window.get(), gfx::Point(5, 5),
      ui::DragDropTypes::DRAG_MOVE, ui::mojom::DragEventSource::kTouch);

  // Should create a captue delegate which takes capture from the window.
  EXPECT_TRUE(drag_drop_controller_->get_capture_delegate());

  drag_drop_controller_.reset();
}

// Regression test for crbug.com/1297209.
// In tablet mode split view, with the browser tab strip on one side and desks
// overview (or any other window) on the other, touch and hold a desk mini view
// (or that other window) and drag a browser tab simultaneously.
TEST_F(DragDropControllerTest, TabletSplitViewDragTwoBrowserTabs) {
  // Enter tablet mode. Avoid TabletModeController::OnGetSwitchStates() from
  // disabling tablet mode.
  base::RunLoop().RunUntilIdle();
  ash::TabletModeControllerTestApi().EnterTabletMode();

  // Enter tablet split view mode by snapping a tab window on each side.
  // A generic top level window is enough to fake a chrome tab.
  std::unique_ptr<aura::Window> tab_window1 = CreateToplevelTestWindow();
  std::unique_ptr<aura::Window> tab_window2 = CreateToplevelTestWindow();
  SplitViewController* const split_view_controller =
      SplitViewController::Get(tab_window1.get());
  split_view_controller->SnapWindow(tab_window1.get(), SnapPosition::kPrimary);
  split_view_controller->SnapWindow(tab_window2.get(),
                                    SnapPosition::kSecondary);
  EXPECT_TRUE(split_view_controller->InTabletSplitViewMode());

  // Touch and hold the right tab window.
  GetEventGenerator()->PressTouch(
      tab_window2->GetBoundsInScreen().CenterPoint());

  // Prepare to drag the left tab window.
  EXPECT_CALL(*mock_shell_delegate(), IsTabDrag(_)).WillOnce(Return(true));

  // Drag and drop needs a drag image to work.
  auto data = CreateDragData(/*with_image=*/true);

  // Start drag and drop on the left tab window.
  auto drag_operation = drag_drop_controller_->StartDragAndDrop(
      std::move(data), tab_window1->GetRootWindow(), tab_window1.get(),
      tab_window1->GetBoundsInScreen().CenterPoint(),
      ui::DragDropTypes::DRAG_MOVE, ui::mojom::DragEventSource::kTouch);
  EXPECT_EQ(drag_operation, DragOperation::kNone);
  EXPECT_FALSE(drag_drop_controller_->IsDragDropInProgress());
  EXPECT_FALSE(drag_drop_controller_->get_capture_delegate());
  EXPECT_FALSE(tab_window1->HasObserver(drag_drop_controller_.get()));
}

TEST_F(DragDropControllerTest, DragImageWidgetNotCreatedIfNoImage) {
  std::unique_ptr<views::Widget> widget = CreateFramelessWidget();
  aura::Window* window = widget->GetNativeWindow();

  auto data = CreateDragData(/*with_image=*/false);
  drag_drop_controller_->StartDragAndDrop(
      std::move(data), window->GetRootWindow(), window, gfx::Point(5, 5),
      ui::DragDropTypes::DRAG_MOVE, ui::mojom::DragEventSource::kMouse);
  EXPECT_FALSE(GetDragImageWindow());
  drag_drop_controller_->DragCancel();

  data = CreateDragData(/*with_image=*/true);
  drag_drop_controller_->StartDragAndDrop(
      std::move(data), window->GetRootWindow(), window, gfx::Point(5, 5),
      ui::DragDropTypes::DRAG_MOVE, ui::mojom::DragEventSource::kMouse);
  EXPECT_TRUE(GetDragImageWindow());
}

// Verifies drag-and-drop with a data transfer policy controller.
class DragDropControllerDlpTest : public DragDropControllerTest {
 public:
  // DragDropControllerTest:
  void SetUp() override {
    DragDropControllerTest::SetUp();

    window_.reset(CreateTestWindowInShellWithDelegate(
        aura::test::TestWindowDelegate::CreateSelfDestroyingDelegate(),
        /*id=*/-1, gfx::Rect(0, 0, 100, 100)));
    delegate_ = std::make_unique<EventTargetTestDelegate>(window_.get());
    aura::client::SetDragDropDelegate(window_.get(), delegate_.get());
    drag_and_drop_observer_ = std::make_unique<NiceMock<MockDragDropObserver>>(
        drag_drop_controller_.get());
  }

  void TearDown() override {
    drag_and_drop_observer_.reset();
    delegate_.reset();
    window_.reset();

    DragDropControllerTest::TearDown();
  }

  // Performs drag-and-drop on `window_` with the specified drag data. Data drop
  // is allowed or not by `dlp_contoller_`.
  void PerformDlpDragAndDrop(std::unique_ptr<ui::OSExchangeData> drag_data) {
    // Posted task will be run when the inner loop runs in StartDragAndDrop.
    ui::test::EventGenerator generator(window_->GetRootWindow(), window_.get());
    generator.PressLeftButton();

    drag_drop_controller_->StartDragAndDrop(
        std::move(drag_data), window_->GetRootWindow(), window_.get(),
        gfx::Point(5, 5), ui::DragDropTypes::DRAG_MOVE,
        ui::mojom::DragEventSource::kMouse);

    // For drag enter
    generator.MoveMouseBy(0, 1);
    // For drag update
    generator.MoveMouseBy(0, 1);
    // For perform drop
    generator.ReleaseLeftButton();
  }

  // A mock data transfer policy controller. Customized to allow/disallow data
  // drop in tests.
  ui::MockDataTransferPolicyController dlp_contoller_;

  std::unique_ptr<EventTargetTestDelegate> delegate_;

  std::unique_ptr<aura::Window> window_;

  // A mock drag-and-drop observer to verify the API function calling order.
  std::unique_ptr<NiceMock<MockDragDropObserver>> drag_and_drop_observer_;
};

// Tests when drop is allowed synchronously.
TEST_F(DragDropControllerDlpTest, AllowedSyncDragDrop) {
  {
    testing::InSequence s;
    EXPECT_CALL(*drag_and_drop_observer_, OnDragStarted);
    EXPECT_CALL(*drag_and_drop_observer_, OnDragCompleted);
    EXPECT_CALL(*drag_and_drop_observer_,
                OnDropCompleted(ui::mojom::DragOperation::kMove));
  }

  // Configure `dlp_controller_` to allow sync drop.
  EXPECT_CALL(dlp_contoller_, DropIfAllowed(_, _, _, _))
      .WillOnce([&](std::optional<ui::DataTransferEndpoint> data_src,
                    std::optional<ui::DataTransferEndpoint> data_dst,
                    std::optional<std::vector<ui::FileInfo>> filenames,
                    base::OnceClosure drop_cb) { std::move(drop_cb).Run(); });

  PerformDlpDragAndDrop(CreateDragData(/*with_image=*/false));

  EXPECT_EQ(EventTargetTestDelegate::State::kPerformDropInvoked,
            delegate_->state());
}

// Tests when drag is cancelled before drop.
TEST_F(DragDropControllerDlpTest, CancelDragBeforeDrop) {
  // Observers should not be notified of drop completion since the async drop
  // should be interrupted by a new drag-and-drop session.
  EXPECT_CALL(*drag_and_drop_observer_, OnDropCompleted).Times(0);

  {
    testing::InSequence s;
    EXPECT_CALL(*drag_and_drop_observer_, OnDragStarted);
    EXPECT_CALL(*drag_and_drop_observer_, OnDragCancelled);
  }

  // Drag to `window_`.
  ui::test::EventGenerator generator(window_->GetRootWindow(), window_.get());
  generator.PressLeftButton();
  drag_drop_controller_->StartDragAndDrop(
      CreateDragData(/*with_image=*/true), window_->GetRootWindow(),
      window_.get(), gfx::Point(5, 5), ui::DragDropTypes::DRAG_MOVE,
      ui::mojom::DragEventSource::kMouse);
  generator.MoveMouseBy(0, 1);

  // Cancel before drop.
  drag_drop_controller_->DragCancel();
  generator.ReleaseLeftButton();

  // There is a non-empty drag image, an animation is expected to be run for
  // cancellation.
  EXPECT_TRUE(cancel_animation());
  EXPECT_TRUE(GetDragImageWindow());
  EXPECT_EQ(EventTargetTestDelegate::State::kDragExitInvoked,
            delegate_->state());
}

// Tests when drop is allowed asynchronously.
TEST_F(DragDropControllerDlpTest, AllowedAsyncDrop) {
  {
    testing::InSequence s;
    EXPECT_CALL(*drag_and_drop_observer_, OnDragStarted);
    EXPECT_CALL(*drag_and_drop_observer_, OnDragCompleted);
    EXPECT_CALL(*drag_and_drop_observer_, OnDropCompleted);
  }

  // Hold the drop callback passed to `dlp_controller_` then run this drop
  // callback later. It emulates a successful async drop.
  base::OnceClosure drop_callback;
  EXPECT_CALL(dlp_contoller_, DropIfAllowed(_, _, _, _))
      .WillOnce([&](std::optional<ui::DataTransferEndpoint> data_src,
                    std::optional<ui::DataTransferEndpoint> data_dst,
                    std::optional<std::vector<ui::FileInfo>> filenames,
                    base::OnceClosure drop_cb) {
        drop_callback = std::move(drop_cb);
      });

  PerformDlpDragAndDrop(CreateDragData(/*with_image=*/true));
  std::move(drop_callback).Run();

  // Check that there is no drag-and-drop in progress after the async drop.
  EXPECT_FALSE(drag_drop_controller_->IsDragDropInProgress());
}

// Tests when the first drop is allowed after the second drag-and-drop session
// starts.
TEST_F(DragDropControllerDlpTest, InterruptedAsyncDrop) {
  // Since the second drag-and-drop session starts before the first drop is
  // completed, an observer should not be notified of the first drop completion.
  EXPECT_CALL(*drag_and_drop_observer_, OnDropCompleted).Times(0);
  {
    testing::InSequence s;
    EXPECT_CALL(*drag_and_drop_observer_, OnDragStarted);
    EXPECT_CALL(*drag_and_drop_observer_, OnDragCompleted);
    EXPECT_CALL(*drag_and_drop_observer_, OnDragStarted);
  }

  base::OnceClosure drop_callback;
  EXPECT_CALL(dlp_contoller_, DropIfAllowed(_, _, _, _))
      .WillOnce([&](std::optional<ui::DataTransferEndpoint> data_src,
                    std::optional<ui::DataTransferEndpoint> data_dst,
                    std::optional<std::vector<ui::FileInfo>> filenames,
                    base::OnceClosure drop_cb) {
        drop_callback = std::move(drop_cb);
      });

  PerformDlpDragAndDrop(CreateDragData(/*with_image=*/true));
  EXPECT_FALSE(cancel_animation());
  EXPECT_FALSE(GetDragImageWindow());

  auto data = std::make_unique<ui::OSExchangeData>();
  data->SetString(u"I am being dragged 2");
  drag_drop_controller_->StartDragAndDrop(
      std::move(data), window_->GetRootWindow(), window_.get(),
      gfx::Point(5, 5), ui::DragDropTypes::DRAG_MOVE,
      ui::mojom::DragEventSource::kMouse);

  // Run `drop_callback` after the second drag-and-drop starts.
  std::move(drop_callback).Run();

  EXPECT_EQ(EventTargetTestDelegate::State::kDragUpdateInvoked,
            delegate_->state());
}

// Tests when drop is disallowed asyncly.
TEST_F(DragDropControllerDlpTest, DlpDisallowAsyncDrop) {
  {
    testing::InSequence s;
    EXPECT_CALL(*drag_and_drop_observer_, OnDragStarted);
    EXPECT_CALL(*drag_and_drop_observer_, OnDragCompleted);
    EXPECT_CALL(*drag_and_drop_observer_, OnDragCancelled);
  }

  // Hold the drop callback passed to `dlp_controller_`. Because `drop_callback`
  // does not run, it emulates an async disallowed drop.
  base::OnceClosure drop_callback;
  EXPECT_CALL(dlp_contoller_, DropIfAllowed(_, _, _, _))
      .WillOnce([&](std::optional<ui::DataTransferEndpoint> data_src,
                    std::optional<ui::DataTransferEndpoint> data_dst,
                    std::optional<std::vector<ui::FileInfo>> filenames,
                    base::OnceClosure drop_cb) {
        drop_callback = std::move(drop_cb);
      });

  PerformDlpDragAndDrop(CreateDragData(/*with_image=*/true));
}

class MouseOrTouchDragDropControllerTest
    : public DragDropControllerTest,
      public testing::WithParamInterface<bool> {
 public:
  MouseOrTouchDragDropControllerTest() = default;
  MouseOrTouchDragDropControllerTest(
      const MouseOrTouchDragDropControllerTest&) = delete;
  MouseOrTouchDragDropControllerTest& operator=(
      const MouseOrTouchDragDropControllerTest&) = delete;
  ~MouseOrTouchDragDropControllerTest() = default;

  bool IsMouse() const { return GetParam(); }

  void Press(ui::test::EventGenerator& generator) {
    if (IsMouse()) {
      generator.PressLeftButton();
    } else {
      generator.PressTouch();
      // long press requires > 1.15s
      task_environment()->AdvanceClock(base::Seconds(2));
    }
  }
  void MoveBy(ui::test::EventGenerator& generator, int x, int y) {
    if (IsMouse())
      generator.MoveMouseBy(x, y);
    else
      generator.MoveTouchBy(x, y);
  }

  void Release(ui::test::EventGenerator& generator) {
    if (IsMouse())
      generator.ReleaseLeftButton();
    else
      generator.ReleaseTouch();
  }
};

// Ensures that the drag drop continues after window destruction.
TEST_P(MouseOrTouchDragDropControllerTest, WindowDestroyedDuringDragDrop) {
  std::unique_ptr<views::Widget> widget = CreateFramelessWidget();
  DragTestView* drag_view = new DragTestView;
  AddViewToWidgetAndResize(widget.get(), drag_view);
  aura::Window* window = widget->GetNativeView();

  EventTargetTestDelegate delegate(window);
  aura::client::SetDragDropDelegate(window, &delegate);

  ui::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
                                     widget->GetNativeView());
  Press(generator);

  int n = 0;
  bool dragged = false;
  auto loop_task = [&](bool inside) {
    MoveBy(generator, 0, 1);
    base::RunLoop().RunUntilIdle();
    n++;
    if (inside && window) {
      dragged = true;
      if (IsMouse()) {
        EXPECT_EQ(window, GetDragWindow());
        EXPECT_GT(n, drag_view->VerticalDragThreshold());
      } else if (n > 5) {
        EXPECT_EQ(window, GetDragWindow());
      }
    }

    if (n == 18) {
      widget->CloseNow();
      EXPECT_FALSE(this->GetDragWindow());
      window = nullptr;
    }
    if (n == 100)
      Release(generator);
  };
  RunWithClosure(base::BindLambdaForTesting(loop_task));
  EXPECT_TRUE(dragged);
  EXPECT_FALSE(GetDragWindow());
  EXPECT_GT(n, 50);

  EXPECT_TRUE(drag_drop_controller_->drag_start_received_);
  EXPECT_FALSE(drag_drop_controller_->drag_canceled_);
  EXPECT_TRUE(drag_drop_controller_->drop_received_);
  EXPECT_EQ(EventTargetTestDelegate::State::kDragExitInvoked, delegate.state());
}

INSTANTIATE_TEST_SUITE_P(All,
                         MouseOrTouchDragDropControllerTest,
                         testing::Bool());

namespace {

class DragDropControllerLongTapCancelTest : public DragDropControllerTest {
 public:
  DragDropControllerLongTapCancelTest(
      const DragDropControllerLongTapCancelTest& other) = delete;
  DragDropControllerLongTapCancelTest& operator=(
      const DragDropControllerLongTapCancelTest& other) = delete;

 protected:
  DragDropControllerLongTapCancelTest() = default;
  ~DragDropControllerLongTapCancelTest() override = default;

  void SetUp() override {
    DragDropControllerTest::SetUp();

    widget_ = CreateFramelessWidget();
    drag_view_ = new DragTestView;
    AddViewToWidgetAndResize(widget_.get(), drag_view_);
    generator_ = std::make_unique<ui::test::EventGenerator>(
        Shell::GetPrimaryRootWindow(), widget_->GetNativeView());
  }

  void IssueLongTap() {
    auto loop_task = [this](bool inside) {
      gfx::Point point = gfx::Rect(drag_view_->bounds()).CenterPoint();
      if (!inside) {
        generator_->PressTouch();
        DispatchGesture(ui::EventType::kGestureLongPress, point);
      } else {
        ASSERT_FALSE(inside_loop_task_executed_);
        inside_loop_task_executed_ = true;

        EXPECT_FALSE(drag_view_->long_tap_received_);
        DispatchGesture(ui::EventType::kGestureLongTap, point);
      }
    };
    RunWithClosure(base::BindLambdaForTesting(loop_task));
  }

  std::unique_ptr<views::Widget> widget_;
  raw_ptr<DragTestView> drag_view_ = nullptr;
  std::unique_ptr<ui::test::EventGenerator> generator_;
  bool inside_loop_task_executed_ = false;
};

}  // namespace

TEST_F(DragDropControllerLongTapCancelTest, TouchDragDropCancelsOnLongTap) {
  IssueLongTap();

  EXPECT_TRUE(drag_drop_controller_->drag_start_received_);
  EXPECT_TRUE(drag_drop_controller_->drag_canceled_);
  EXPECT_EQ(0, drag_drop_controller_->num_drag_updates_);
  EXPECT_FALSE(drag_drop_controller_->drop_received_);
  EXPECT_EQ(u"I am being dragged", drag_drop_controller_->drag_string_);
  EXPECT_EQ(0, drag_view_->num_drag_enters_);
  EXPECT_EQ(0, drag_view_->num_drops_);
  EXPECT_EQ(0, drag_view_->num_drag_exits_);
  EXPECT_TRUE(drag_view_->drag_done_received_);

  // The long tap gesture is expected to be forwarded after the cancel
  // animation.
  ASSERT_TRUE(cancel_animation());
  EXPECT_FALSE(drag_view_->long_tap_received_);
  CompleteCancelAnimation();
  EXPECT_TRUE(drag_view_->long_tap_received_);
}

TEST_F(DragDropControllerLongTapCancelTest,
       LongTapForwardedWithoutCancelAnimation) {
  drag_view_->OmitDragImage();

  // DragDropController does not support touch drag/drop without a drag image,
  // unless it has a non-null |toplevel_window_drag_delegate_|.
  TestToplevelWindowDragDelegate delegate;
  drag_drop_controller_->set_toplevel_window_drag_delegate(&delegate);

  IssueLongTap();

  ASSERT_FALSE(cancel_animation());
  EXPECT_TRUE(drag_view_->long_tap_received_);
}

}  // namespace ash