910e62b5创建于 1月15日历史提交
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/devtools/protocol/input_handler.h"

#include <stddef.h>

#include <memory>
#include <tuple>
#include <utility>
#include <vector>

#include "base/check.h"
#include "base/check_deref.h"
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "base/trace_event/trace_event.h"
#include "base/types/expected.h"
#include "components/input/render_widget_host_input_event_router.h"
#include "content/browser/devtools/devtools_agent_host_impl.h"
#include "content/browser/devtools/protocol/native_input_event_builder.h"
#include "content/browser/devtools/protocol/protocol.h"
#include "content/browser/renderer_host/data_transfer_util.h"
#include "content/browser/renderer_host/input/touch_emulator_impl.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/renderer_host/render_widget_host_impl.h"
#include "content/browser/renderer_host/render_widget_host_view_base.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/common/input/synthetic_pinch_gesture.h"
#include "content/common/input/synthetic_pinch_gesture_params.h"
#include "content/common/input/synthetic_pointer_action.h"
#include "content/common/input/synthetic_pointer_driver.h"
#include "content/common/input/synthetic_smooth_scroll_gesture.h"
#include "content/common/input/synthetic_smooth_scroll_gesture_params.h"
#include "content/common/input/synthetic_tap_gesture.h"
#include "content/common/input/synthetic_tap_gesture_params.h"
#include "content/public/common/content_features.h"
#include "third_party/blink/public/common/input/web_input_event.h"
#include "third_party/blink/public/common/page/page_zoom.h"
#include "ui/base/dragdrop/mojom/drag_drop_types.mojom-shared.h"
#include "ui/events/base_event_utils.h"
#include "ui/events/blink/web_input_event_traits.h"
#include "ui/events/event_constants.h"
#include "ui/events/gesture_detection/gesture_provider_config_helper.h"
#include "ui/events/keycodes/dom/keycode_converter.h"
#include "ui/gfx/geometry/point_f.h"
#include "ui/gfx/range/range.h"

namespace content::protocol {

namespace {

gfx::PointF CssPixelsToPointF(double x, double y, float scale_factor) {
  return gfx::PointF(x * scale_factor, y * scale_factor);
}

gfx::Vector2dF CssPixelsToVector2dF(double x, double y, float scale_factor) {
  return gfx::Vector2dF(x * scale_factor, y * scale_factor);
}

bool StringToGestureSourceType(std::optional<std::string> in,
                               content::mojom::GestureSourceType& out) {
  if (!in.has_value()) {
    out = content::mojom::GestureSourceType::kDefaultInput;
    return true;
  }
  if (in.value() == Input::GestureSourceTypeEnum::Default) {
    out = content::mojom::GestureSourceType::kDefaultInput;
    return true;
  }
  if (in.value() == Input::GestureSourceTypeEnum::Touch) {
    out = content::mojom::GestureSourceType::kTouchInput;
    return true;
  }
  if (in.value() == Input::GestureSourceTypeEnum::Mouse) {
    out = content::mojom::GestureSourceType::kMouseInput;
    return true;
  }
  return false;
}

int GetEventModifiers(int modifiers,
                      bool auto_repeat,
                      bool is_keypad,
                      int location,
                      int buttons) {
  int result = blink::WebInputEvent::kFromDebugger;
  if (auto_repeat)
    result |= blink::WebInputEvent::kIsAutoRepeat;
  if (is_keypad)
    result |= blink::WebInputEvent::kIsKeyPad;

  if (modifiers & 1)
    result |= blink::WebInputEvent::kAltKey;
  if (modifiers & 2)
    result |= blink::WebInputEvent::kControlKey;
  if (modifiers & 4)
    result |= blink::WebInputEvent::kMetaKey;
  if (modifiers & 8)
    result |= blink::WebInputEvent::kShiftKey;

  if (location & 1)
    result |= blink::WebInputEvent::kIsLeft;
  if (location & 2)
    result |= blink::WebInputEvent::kIsRight;

  if (buttons & 1)
    result |= blink::WebMouseEvent::kLeftButtonDown;
  if (buttons & 2)
    result |= blink::WebInputEvent::kRightButtonDown;
  if (buttons & 4)
    result |= blink::WebInputEvent::kMiddleButtonDown;
  if (buttons & 8)
    result |= blink::WebInputEvent::kBackButtonDown;
  if (buttons & 16)
    result |= blink::WebInputEvent::kForwardButtonDown;
  return result;
}

base::TimeTicks GetEventTimeTicks(const std::optional<double>& timestamp) {
  // Convert timestamp, in seconds since unix epoch, to an event timestamp
  // which is time ticks since platform start time.
  return timestamp.has_value()
             ? base::Seconds(timestamp.value()) + base::TimeTicks::UnixEpoch()
             : base::TimeTicks::Now();
}

bool SetKeyboardEventText(
    base::span<char16_t, blink::WebKeyboardEvent::kTextLengthCap> to,
    std::optional<std::string> from) {
  if (!from.has_value()) {
    return true;
  }

  std::u16string text16 = base::UTF8ToUTF16(from.value());
  if (text16.size() >= to.size()) {
    return false;
  }

  base::span<char16_t> to_text;
  base::span<char16_t> to_nul;
  std::tie(to_text, to_nul) = to.split_at(text16.size());
  to_text.copy_from(text16);
  to_nul.front() = 0;
  return true;
}

bool GetMouseEventButton(const std::string& button,
                         blink::WebPointerProperties::Button* event_button,
                         int* event_modifiers) {
  *event_modifiers = blink::WebInputEvent::kFromDebugger;
  if (button.empty())
    return true;

  if (button == Input::MouseButtonEnum::None) {
    *event_button = blink::WebMouseEvent::Button::kNoButton;
  } else if (button == Input::MouseButtonEnum::Left) {
    *event_button = blink::WebMouseEvent::Button::kLeft;
    *event_modifiers |= blink::WebInputEvent::kLeftButtonDown;
  } else if (button == Input::MouseButtonEnum::Middle) {
    *event_button = blink::WebMouseEvent::Button::kMiddle;
    *event_modifiers |= blink::WebInputEvent::kMiddleButtonDown;
  } else if (button == Input::MouseButtonEnum::Right) {
    *event_button = blink::WebMouseEvent::Button::kRight;
    *event_modifiers |= blink::WebInputEvent::kRightButtonDown;
  } else if (button == Input::MouseButtonEnum::Back) {
    *event_button = blink::WebMouseEvent::Button::kBack;
    *event_modifiers |= blink::WebInputEvent::kBackButtonDown;
  } else if (button == Input::MouseButtonEnum::Forward) {
    *event_button = blink::WebMouseEvent::Button::kForward;
    *event_modifiers |= blink::WebInputEvent::kForwardButtonDown;
  } else {
    return false;
  }
  return true;
}

blink::WebInputEvent::Type GetMouseEventType(const std::string& type) {
  if (type == Input::DispatchMouseEvent::TypeEnum::MousePressed)
    return blink::WebInputEvent::Type::kMouseDown;
  if (type == Input::DispatchMouseEvent::TypeEnum::MouseReleased)
    return blink::WebInputEvent::Type::kMouseUp;
  if (type == Input::DispatchMouseEvent::TypeEnum::MouseMoved)
    return blink::WebInputEvent::Type::kMouseMove;
  if (type == Input::DispatchMouseEvent::TypeEnum::MouseWheel)
    return blink::WebInputEvent::Type::kMouseWheel;
  return blink::WebInputEvent::Type::kUndefined;
}

blink::WebInputEvent::Type GetTouchEventType(const std::string& type) {
  if (type == Input::DispatchTouchEvent::TypeEnum::TouchStart)
    return blink::WebInputEvent::Type::kTouchStart;
  if (type == Input::DispatchTouchEvent::TypeEnum::TouchEnd)
    return blink::WebInputEvent::Type::kTouchEnd;
  if (type == Input::DispatchTouchEvent::TypeEnum::TouchMove)
    return blink::WebInputEvent::Type::kTouchMove;
  if (type == Input::DispatchTouchEvent::TypeEnum::TouchCancel)
    return blink::WebInputEvent::Type::kTouchCancel;
  return blink::WebInputEvent::Type::kUndefined;
}

blink::WebPointerProperties::PointerType GetPointerType(
    const std::string& type) {
  if (type == Input::DispatchMouseEvent::PointerTypeEnum::Mouse)
    return blink::WebPointerProperties::PointerType::kMouse;
  if (type == Input::DispatchMouseEvent::PointerTypeEnum::Pen)
    return blink::WebPointerProperties::PointerType::kPen;
  return blink::WebPointerProperties::PointerType::kMouse;
}

SyntheticPointerActionParams::PointerActionType GetTouchPointerActionType(
    const std::string& type) {
  if (type == Input::DispatchTouchEvent::TypeEnum::TouchStart)
    return SyntheticPointerActionParams::PointerActionType::PRESS;
  if (type == Input::DispatchTouchEvent::TypeEnum::TouchEnd)
    return SyntheticPointerActionParams::PointerActionType::RELEASE;
  if (type == Input::DispatchTouchEvent::TypeEnum::TouchMove)
    return SyntheticPointerActionParams::PointerActionType::MOVE;
  if (type == Input::DispatchTouchEvent::TypeEnum::TouchCancel)
    return SyntheticPointerActionParams::PointerActionType::CANCEL;
  return SyntheticPointerActionParams::PointerActionType::NOT_INITIALIZED;
}

bool GenerateTouchPoints(
    blink::WebTouchEvent* event,
    blink::WebInputEvent::Type type,
    const base::flat_map<int, blink::WebTouchPoint>& points,
    const blink::WebTouchPoint& changing) {
  event->touches_length = 1;
  event->touches[0] = changing;
  for (auto& it : points) {
    if (it.first == changing.id)
      continue;
    if (event->touches_length == blink::WebTouchEvent::kTouchesLengthCap)
      return false;
    event->touches[event->touches_length] = it.second;
    event->touches[event->touches_length].state =
        type == blink::WebInputEvent::Type::kTouchCancel
            ? blink::WebTouchPoint::State::kStateCancelled
            : blink::WebTouchPoint::State::kStateStationary;
    event->touches_length++;
  }
  if (type == blink::WebInputEvent::Type::kTouchCancel ||
      type == blink::WebInputEvent::Type::kTouchEnd) {
    event->touches[0].state = type == blink::WebInputEvent::Type::kTouchCancel
                                  ? blink::WebTouchPoint::State::kStateCancelled
                                  : blink::WebTouchPoint::State::kStateReleased;
    event->SetType(type);
  } else if (!base::Contains(points, changing.id)) {
    event->touches[0].state = blink::WebTouchPoint::State::kStatePressed;
    event->SetType(blink::WebInputEvent::Type::kTouchStart);
  } else {
    event->touches[0].state = blink::WebTouchPoint::State::kStateMoved;
    event->SetType(blink::WebInputEvent::Type::kTouchMove);
  }
  return true;
}

std::string ValidatePointerEventProperties(double force,
                                           double tangential_pressure,
                                           double tilt_x,
                                           double tilt_y,
                                           int twist) {
  if (force < 0.0f || force > 1.0f) {
    return "'force' should be in the range of [0,1]";
  }
  if (tangential_pressure < -1.0f || tangential_pressure > 1.0f) {
    return "'tangential_pressure' should be in the range of [-1,1]";
  }
  if (tilt_x < -90.0f || tilt_x > 90.0f) {
    return "'tilt_x' should be in the range of [-90,90]";
  }
  if (tilt_y < -90.0f || tilt_y > 90.0f) {
    return "'tilt_y' should be in the range of [-90,90]";
  }
  if (twist < 0 || twist > 359)
    return "'twist' should be in the range of [0,359]";
  return "";
}

void SendSynthesizePinchGestureResponse(
    std::unique_ptr<Input::Backend::SynthesizePinchGestureCallback> callback,
    SyntheticGesture::Result result) {
  if (result == SyntheticGesture::Result::GESTURE_FINISHED) {
    callback->sendSuccess();
  } else {
    callback->sendFailure(Response::ServerError(
        base::StringPrintf("Synthetic pinch failed, result was %d", result)));
  }
}

class TapGestureResponse {
 public:
  TapGestureResponse(
      std::unique_ptr<Input::Backend::SynthesizeTapGestureCallback> callback,
      int count)
      : callback_(std::move(callback)),
        count_(count) {
  }

  void OnGestureResult(SyntheticGesture::Result result) {
    --count_;
    // Still waiting for more taps to finish.
    if (result == SyntheticGesture::Result::GESTURE_FINISHED && count_)
      return;
    if (callback_) {
      if (result == SyntheticGesture::Result::GESTURE_FINISHED) {
        callback_->sendSuccess();
      } else {
        callback_->sendFailure(Response::ServerError(
            base::StringPrintf("Synthetic tap failed, result was %d", result)));
      }
      callback_.reset();
    }
    if (!count_)
      delete this;
  }

 private:
  std::unique_ptr<Input::Backend::SynthesizeTapGestureCallback> callback_;
  int count_;
};

void SendSynthesizeScrollGestureResponse(
    std::unique_ptr<Input::Backend::SynthesizeScrollGestureCallback> callback,
    SyntheticGesture::Result result) {
  if (result == SyntheticGesture::Result::GESTURE_FINISHED) {
    callback->sendSuccess();
  } else {
    callback->sendFailure(Response::ServerError(
        base::StringPrintf("Synthetic scroll failed, result was %d", result)));
  }
}

void DispatchPointerActionsResponse(
    std::unique_ptr<Input::Backend::DispatchTouchEventCallback> callback,
    SyntheticGesture::Result result) {
  if (result == SyntheticGesture::Result::GESTURE_FINISHED) {
    callback->sendSuccess();
  } else {
    callback->sendFailure(Response::ServerError(
        base::StringPrintf("Action sequence failed, result was %d", result)));
  }
}

DropData ProtocolDragDataToDropData(std::unique_ptr<Input::DragData> data) {
  std::vector<blink::mojom::DragItemPtr> items;

  for (const auto& item : *data->GetItems()) {
    blink::mojom::DragItemStringPtr mojo_item =
        blink::mojom::DragItemString::New();
    mojo_item->string_type = item->GetMimeType();
    mojo_item->string_data = base::UTF8ToUTF16(item->GetData());
    if (item->HasBaseURL())
      mojo_item->base_url = GURL(item->GetBaseURL(""));
    if (item->HasTitle())
      mojo_item->title = base::UTF8ToUTF16(item->GetTitle(""));
    items.push_back(blink::mojom::DragItem::NewString(std::move(mojo_item)));
  }

  blink::mojom::DragDataPtr mojo_data = blink::mojom::DragData::New(
      std::move(items), std::nullopt,
      /*force_default_action=*/false, network::mojom::ReferrerPolicy::kDefault);
  DropData drop_data = DragDataToDropData(*mojo_data);

  protocol::Array<protocol::String> default_value;
  for (const auto& file : *data->GetFiles(&default_value)) {
    drop_data.filenames.emplace_back(base::FilePath::FromUTF8Unsafe(file),
                                     base::FilePath());
  }

  return drop_data;
}

base::expected<std::unique_ptr<blink::WebMouseEvent>, protocol::Response>
CreateWebMouseEvent(const std::string& event_type,
                    double x,
                    double y,
                    float scale_factor,
                    std::optional<int> modifiers,
                    std::optional<double> timestamp,
                    std::optional<std::string> button,
                    std::optional<int> buttons,
                    std::optional<int> click_count,
                    std::optional<double> force,
                    std::optional<double> tangential_pressure,
                    std::optional<double> tilt_x,
                    std::optional<double> tilt_y,
                    std::optional<int> twist,
                    std::optional<double> delta_x,
                    std::optional<double> delta_y,
                    std::optional<std::string> pointer_type) {
  blink::WebInputEvent::Type type = GetMouseEventType(event_type);
  if (type == blink::WebInputEvent::Type::kUndefined) {
    return base::unexpected(Response::InvalidParams(
        base::StringPrintf("Unexpected event type '%s'", event_type.c_str())));
  }

  blink::WebPointerProperties::Button event_button =
      blink::WebPointerProperties::Button::kNoButton;
  int button_modifiers = 0;
  if (!GetMouseEventButton(button.value_or(""), &event_button,
                           &button_modifiers)) {
    return base::unexpected(Response::InvalidParams("Invalid mouse button"));
  }

  int event_modifiers =
      GetEventModifiers(modifiers.value_or(blink::WebInputEvent::kNoModifiers),
                        false, false, 0, buttons.value_or(0));
  event_modifiers |= button_modifiers;
  base::TimeTicks event_timestamp = GetEventTimeTicks(timestamp);

  std::unique_ptr<blink::WebMouseEvent> mouse_event;

  if (type == blink::WebInputEvent::Type::kMouseWheel) {
    auto wheel_event = std::make_unique<blink::WebMouseWheelEvent>(
        type, event_modifiers, event_timestamp);
    if (!delta_x.has_value() || !delta_y.has_value()) {
      return base::unexpected(Response::InvalidParams(
          "'deltaX' and 'deltaY' are expected for mouseWheel event"));
    }
    wheel_event->delta_x = static_cast<float>(-delta_x.value());
    wheel_event->delta_y = static_cast<float>(-delta_y.value());
    if (wheel_event->delta_x != 0.0f) {
      wheel_event->wheel_ticks_x = wheel_event->delta_x > 0.0f ? 1.0f : -1.0f;
    }
    if (wheel_event->delta_y != 0.0f) {
      wheel_event->wheel_ticks_y = wheel_event->delta_y > 0.0f ? 1.0f : -1.0f;
    }
    wheel_event->phase = blink::WebMouseWheelEvent::kPhaseBegan;
    wheel_event->delta_units = ui::ScrollGranularity::kScrollByPrecisePixel;
    wheel_event->dispatch_type = blink::WebInputEvent::DispatchType::kBlocking;
    mouse_event = std::move(wheel_event);
  } else {
    mouse_event = std::make_unique<blink::WebMouseEvent>(type, event_modifiers,
                                                         event_timestamp);
    std::string message = ValidatePointerEventProperties(
        force.value_or(0), tangential_pressure.value_or(0), tilt_x.value_or(0),
        tilt_y.value_or(0), twist.value_or(0));
    if (!message.empty()) {
      return base::unexpected(Response::InvalidParams(message));
    }
  }

  mouse_event->button = event_button;
  mouse_event->click_count = click_count.value_or(0);
  mouse_event->pointer_type = GetPointerType(pointer_type.value_or(""));
  mouse_event->force = force.value_or(0);
  mouse_event->tangential_pressure = tangential_pressure.value_or(0);
  mouse_event->tilt_x = tilt_x.value_or(0);
  mouse_event->tilt_y = tilt_y.value_or(0);
  mouse_event->twist = twist.value_or(0);

  mouse_event->SetPositionInWidget(CssPixelsToPointF(x, y, scale_factor));
  mouse_event->SetPositionInScreen(mouse_event->PositionInWidget());

  return mouse_event;
}

base::expected<std::vector<blink::WebTouchEvent>, protocol::Response>
CreateWebTouchEvents(
    const std::string& event_type,
    std::optional<int> modifiers,
    std::optional<double> timestamp,
    float scale_factor,
    base::flat_map<blink::PointerId, blink::WebTouchPoint>& touched_points,
    std::unique_ptr<Array<Input::TouchPoint>> touch_points) {
  blink::WebInputEvent::Type type = GetTouchEventType(event_type);
  if (type == blink::WebInputEvent::Type::kUndefined) {
    return base::unexpected(Response::InvalidParams(
        base::StringPrintf("Unexpected event type '%s'", event_type.c_str())));
  }

  int event_modifiers =
      GetEventModifiers(modifiers.value_or(blink::WebInputEvent::kNoModifiers),
                        false, false, 0, 0);
  base::TimeTicks event_timestamp = GetEventTimeTicks(timestamp);

  if ((type == blink::WebInputEvent::Type::kTouchStart ||
       type == blink::WebInputEvent::Type::kTouchMove) &&
      touch_points->empty()) {
    return base::unexpected(Response::InvalidParams(
        "TouchStart and TouchMove must have at least one touch point."));
  }
  if (type == blink::WebInputEvent::Type::kTouchCancel &&
      !touch_points->empty()) {
    return base::unexpected(
        Response::InvalidParams("TouchCancel must not have any touch points."));
  }
  if (type != blink::WebInputEvent::Type::kTouchStart &&
      touched_points.empty()) {
    return base::unexpected(Response::InvalidParams(
        "Must send a TouchStart first to start a new touch."));
  }

  base::flat_map<blink::PointerId, blink::WebTouchPoint> points;
  size_t with_id = 0;
  for (size_t i = 0; i < touch_points->size(); ++i) {
    Input::TouchPoint* point = (*touch_points)[i].get();
    int id = point->GetId(i);  // index |i| is default for the id.
    if (point->HasId()) {
      with_id++;
    }
    std::string message = ValidatePointerEventProperties(
        point->GetForce(1.0f), point->GetTangentialPressure(0.0f),
        point->GetTiltX(0.0f), point->GetTiltY(0.0f), point->GetTwist(0));
    if (!message.empty()) {
      return base::unexpected(Response::InvalidParams(message));
    }
    points[id].id = id;
    points[id].radius_x = point->GetRadiusX(1.0f);
    points[id].radius_y = point->GetRadiusY(1.0f);
    points[id].rotation_angle = point->GetRotationAngle(0.0f);
    points[id].force = point->GetForce(1.0f);
    points[id].pointer_type = blink::WebPointerProperties::PointerType::kTouch;
    points[id].SetPositionInWidget(
        CssPixelsToPointF(point->GetX(), point->GetY(), scale_factor));
    points[id].SetPositionInScreen(points[id].PositionInScreen());
    points[id].tilt_x = point->GetTiltX(0);
    points[id].tilt_y = point->GetTiltY(0);
    points[id].tangential_pressure = point->GetTangentialPressure(0);
    points[id].twist = point->GetTwist(0);
  }
  if (with_id > 0 && with_id < touch_points->size()) {
    return base::unexpected(Response::InvalidParams(
        "All or none of the provided TouchPoints must supply ids."));
  }

  std::vector<blink::WebTouchEvent> events;
  bool ok = true;
  for (auto& id_point : points) {
    if (base::Contains(touched_points, id_point.first) &&
        type == blink::WebInputEvent::Type::kTouchMove &&
        touched_points[id_point.first].PositionInWidget() ==
            id_point.second.PositionInWidget()) {
      continue;
    }

    events.emplace_back(type, event_modifiers, event_timestamp);
    ok &= GenerateTouchPoints(&events.back(), type, touched_points,
                              id_point.second);
    if (type == blink::WebInputEvent::Type::kTouchStart ||
        type == blink::WebInputEvent::Type::kTouchMove) {
      touched_points[id_point.first] = id_point.second;
    } else if (type == blink::WebInputEvent::Type::kTouchEnd) {
      touched_points.erase(id_point.first);
    }
  }

  if (touch_points->size() == 0 && touched_points.size() > 0) {
    if (type == blink::WebInputEvent::Type::kTouchCancel) {
      events.emplace_back(type, event_modifiers, event_timestamp);
      ok &= GenerateTouchPoints(&events.back(), type, touched_points,
                                touched_points.begin()->second);
      touched_points.clear();
    } else if (type == blink::WebInputEvent::Type::kTouchEnd) {
      for (auto it = touched_points.begin(); it != touched_points.end();) {
        events.emplace_back(type, event_modifiers, event_timestamp);
        ok &= GenerateTouchPoints(&events.back(), type, touched_points,
                                  it->second);
        it = touched_points.erase(it);
      }
    }
  }
  if (!ok) {
    return base::unexpected(Response::ServerError(
        base::StringPrintf("Exceeded maximum touch points limit of %d",
                           blink::WebTouchEvent::kTouchesLengthCap)));
  }

  return events;
}

// Converts a point from the coordinate space of a given RenderWidgetHostView
// to the absolute screen coordinate space.
gfx::PointF ConvertWidgetPointToScreenPoint(
    RenderWidgetHostViewBase& view,
    const gfx::PointF& point_in_widget) {
  gfx::Rect view_bounds_in_screen = view.GetViewBounds();
  return point_in_widget + view_bounds_in_screen.OffsetFromOrigin();
}

}  // namespace

// FailSafe sends a failure to a given backend callback if the wrapper is never
// called. It's _only_ meant to be used when a OnceCallback isn't called due to
// a null WeakPtr. In any other situation, you should call this directly to be
// declarative (even when it's an internal error).
template <class BackendCallback>
class FailSafe {
 public:
  explicit FailSafe(std::unique_ptr<BackendCallback> callback)
      : callback_(std::move(callback)) {}
  ~FailSafe() {
    if (callback_) {
      sendFailure(Response::InternalError());
    }
  }

  FailSafe(const FailSafe& other) = delete;
  FailSafe& operator=(const FailSafe& other) = delete;

  FailSafe(FailSafe&& other) = default;
  FailSafe& operator=(FailSafe&& other) = default;

  void sendSuccess() {
    DCHECK(callback_) << "Already called before.";
    std::move(callback_)->sendSuccess();
  }
  void sendFailure(const DispatchResponse& response) {
    DCHECK(callback_) << "Already called before.";
    std::move(callback_)->sendFailure(response);
  }
  void fallThrough() {
    DCHECK(callback_) << "Already called before.";
    std::move(callback_)->fallthrough();
  }

  std::unique_ptr<BackendCallback> release() { return std::move(callback_); }

 private:
  std::unique_ptr<BackendCallback> callback_;
};

class InputHandler::InputInjector
    : public RenderWidgetHost::InputEventObserver {
 public:
  InputInjector(InputHandler* owner, RenderWidgetHostImpl* widget_host)
      : owner_(owner), widget_host_(widget_host->GetWeakPtr()) {
    widget_host->AddInputEventObserver(this);
    // Make sure the input event observer is not blocked by browser-side
    // paint-holding.
    widget_host_->ForceFirstFrameAfterNavigationTimeout();
  }

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

  void Cleanup() {
    for (auto& callback : pending_key_callbacks_)
      callback->sendSuccess();
    pending_key_callbacks_.clear();
    for (auto& callback : pending_mouse_callbacks_)
      callback->sendSuccess();
    pending_mouse_callbacks_.clear();
    MaybeSelfDestruct();
  }

  bool HasWidgetHost(RenderWidgetHostImpl* widget_host) {
    return widget_host == widget_host_.get();
  }

  void InjectWheelEvent(blink::WebMouseWheelEvent* wheel_event,
                        std::unique_ptr<DispatchMouseEventCallback> callback) {
    if (!widget_host_) {
      callback->sendFailure(Response::InternalError());
      return;
    }

    widget_host_->Focus();
    input_queued_ = false;
    pending_mouse_callbacks_.push_back(std::move(callback));
    // This may destroy the injector if the events get discarded.
    base::WeakPtr<InputHandler::InputInjector> weak_this =
        weak_ptr_factory_.GetWeakPtr();
    widget_host_->ForwardWheelEvent(*wheel_event);
    if (!weak_this) {
      return;
    }
    if (!input_queued_) {
      pending_mouse_callbacks_.back()->sendSuccess();
      pending_mouse_callbacks_.pop_back();
      MaybeSelfDestruct();
      return;
    }

    // Send a synthetic wheel event with phaseEnded to finish scrolling.
    wheel_event->delta_x = 0;
    wheel_event->delta_y = 0;
    wheel_event->wheel_ticks_x = 0;
    wheel_event->wheel_ticks_y = 0;
    wheel_event->phase = blink::WebMouseWheelEvent::kPhaseEnded;
    wheel_event->dispatch_type =
        blink::WebInputEvent::DispatchType::kEventNonBlocking;
    widget_host_->ForwardWheelEvent(*wheel_event);
  }

  void InjectMouseEvent(const blink::WebMouseEvent& mouse_event,
                        std::unique_ptr<DispatchMouseEventCallback> callback) {
    if (!widget_host_) {
      callback->sendFailure(Response::InternalError());
      return;
    }

    if (owner_->drag_controller_.HandleMouseEvent(*widget_host_, mouse_event,
                                                  callback)) {
      return;
    }

    widget_host_->Focus();
    input_queued_ = false;
    pending_mouse_callbacks_.push_back(std::move(callback));
    // This may destroy the injector if the events get discarded.
    base::WeakPtr<InputHandler::InputInjector> weak_this =
        weak_ptr_factory_.GetWeakPtr();
    widget_host_->ForwardMouseEvent(mouse_event);
    if (!weak_this) {
      return;
    }
    if (!input_queued_) {
      pending_mouse_callbacks_.back()->sendSuccess();
      pending_mouse_callbacks_.pop_back();
      MaybeSelfDestruct();
    }
  }

  void InjectKeyboardEvent(const input::NativeWebKeyboardEvent& keyboard_event,
                           std::unique_ptr<Array<std::string>> commands,
                           std::unique_ptr<DispatchKeyEventCallback> callback) {
    if (!widget_host_) {
      callback->sendFailure(Response::InternalError());
      return;
    }

    widget_host_->Focus();
    input_queued_ = false;
    pending_key_callbacks_.push_back(std::move(callback));
    ui::LatencyInfo latency;
    std::vector<blink::mojom::EditCommandPtr> edit_commands;
    if (commands) {
      for (const std::string& command : *commands) {
        edit_commands.push_back(blink::mojom::EditCommand::New(command, ""));
      }
    }
    // This may close the target, for example, if pressing Ctrl+W.
    base::WeakPtr<InputHandler::InputInjector> weak_this =
        weak_ptr_factory_.GetWeakPtr();
    widget_host_->ForwardKeyboardEventWithCommands(keyboard_event, latency,
                                                   std::move(edit_commands));
    if (!weak_this)
      return;
    if (!input_queued_) {
      pending_key_callbacks_.back()->sendSuccess();
      pending_key_callbacks_.pop_back();
      MaybeSelfDestruct();
    }
  }

  void InjectTouchEvents(const std::vector<blink::WebTouchEvent>& events,
                         std::unique_ptr<DispatchTouchEventCallback> callback) {
    if (!widget_host_) {
      callback->sendFailure(Response::InternalError());
      return;
    }

    widget_host_->Focus();
    widget_host_->GetTouchEmulator(/*create_if_necessary=*/true)
        ->Enable(input::TouchEmulator::Mode::kInjectingTouchEvents,
                 ui::GestureProviderConfigType::CURRENT_PLATFORM);
    base::OnceClosure closure = base::BindOnce(
        &DispatchTouchEventCallback::sendSuccess, std::move(callback));
    // This may destroy the injector if the events get discarded.
    base::WeakPtr<InputHandler::InputInjector> weak_this =
        weak_ptr_factory_.GetWeakPtr();
    for (size_t i = 0; i < events.size(); i++) {
      widget_host_->GetTouchEmulator(/*create_if_necessary=*/true)
          ->InjectTouchEvent(events[i], widget_host_->GetView(),
                             i == events.size() - 1 ? std::move(closure)
                                                    : base::OnceClosure());
      if (!weak_this) {
        return;
      }
    }
    MaybeSelfDestruct();
  }

 private:
  void OnInputEvent(const RenderWidgetHost& widget,
                    const blink::WebInputEvent& event,
                    InputEventSource source) override {
    input_queued_ = true;
  }

  void OnInputEventAck(const RenderWidgetHost& widget,
                       blink::mojom::InputEventResultSource source,
                       blink::mojom::InputEventResultState state,
                       const blink::WebInputEvent& event) override {
    if ((event.GetModifiers() & blink::WebInputEvent::kFromDebugger) == 0)
      return;

    if (blink::WebInputEvent::IsKeyboardEventType(event.GetType()) &&
        !pending_key_callbacks_.empty()) {
      pending_key_callbacks_.front()->sendSuccess();
      pending_key_callbacks_.pop_front();
      MaybeSelfDestruct();
      return;
    }

    if ((blink::WebInputEvent::IsMouseEventType(event.GetType()) ||
         event.GetType() == blink::WebInputEvent::Type::kMouseWheel) &&
        !pending_mouse_callbacks_.empty()) {
      auto callback = std::move(pending_mouse_callbacks_.front());
      pending_mouse_callbacks_.pop_front();
      // We need to handle the event in the drag controller in case drag was
      // initiated at some point between dispatch and now because the event will
      // have been ignored during dispatch in this case.
      //
      // Note this also applies to the mouse move that triggers the drag, so
      // HandleMouseEvent has special logic to handle this specific case.
      if (!widget_host_ ||
          !owner_->drag_controller_.HandleMouseEvent(
              *widget_host_, static_cast<const blink::WebMouseEvent&>(event),
              callback)) {
        callback->sendSuccess();
      }
      MaybeSelfDestruct();
      return;
    }
  }

  void MaybeSelfDestruct() {
    if (!pending_key_callbacks_.empty() || !pending_mouse_callbacks_.empty())
      return;
    if (widget_host_)
      widget_host_->RemoveInputEventObserver(this);
    owner_->injectors_.erase(this);
  }

  const raw_ptr<InputHandler> owner_;
  base::WeakPtr<RenderWidgetHostImpl> widget_host_;
  // Callbacks for calls to Input.dispatchKey/MouseEvent that have been sent to
  // the renderer, but that we haven't yet received an ack for.
  bool input_queued_ = false;
  base::circular_deque<std::unique_ptr<DispatchKeyEventCallback>>
      pending_key_callbacks_;
  base::circular_deque<std::unique_ptr<DispatchMouseEventCallback>>
      pending_mouse_callbacks_;
  base::WeakPtrFactory<InputHandler::InputInjector> weak_ptr_factory_{this};
};

struct InputHandler::DragController::DragState {
  DropData data;
  blink::DragOperationsMask mask;
  base::WeakPtr<RenderWidgetHostImpl> host;
  gfx::PointF pos;
  // Acts as a counting semaphore for concurrent updates.
  size_t updating;
  base::OnceClosure updated_callback;
};

struct InputHandler::DragController::InitialState {
  std::unique_ptr<blink::WebMouseEvent> event;
  base::WeakPtr<RenderWidgetHostImpl> host;
};

InputHandler::DragController::DragController(InputHandler& handler)
    : handler_(handler) {}

InputHandler::DragController::~DragController() = default;

bool InputHandler::DragController::HandleMouseEvent(
    RenderWidgetHostImpl& host,
    const blink::WebMouseEvent& event,
    std::unique_ptr<DispatchMouseEventCallback>& callback) {
  if (!drag_state_) {
    switch (event.GetType()) {
      case blink::mojom::EventType::kMouseMove:
        // Check if the user started a mouse down through CDP.
        if (initial_state_) {
          // Set the move event in case dragging starts from this event.
          initial_state_->event = std::make_unique<blink::WebMouseEvent>(event);
          initial_state_->host = host.GetWeakPtr();
        }
        break;
      case blink::mojom::EventType::kMouseDown:
        // If the user performs a mouse down using CDP, then set the initial
        // state. OS dragging is not possible beyond this point.
        initial_state_ = std::make_unique<InitialState>();
        break;
      case blink::mojom::EventType::kMouseUp:
        // If the user performs a mouse up using CDP, then reset the initial
        // state. OS dragging is possible beyond this point.
        initial_state_ = nullptr;
        break;
      default:
        break;
    }
    return false;
  }
  switch (event.GetType()) {
    case blink::mojom::EventType::kMouseMove:
      // It's possible that the mouse movement that starts a drag is acked
      // after dragging starts. When this happens,
      //
      //  1. `StartDragging` will update the drag state with the mouse
      //     movement (through `last_mouse_move_`), then
      //  2. the mouse movement (this time from acking in `OnInputEventAck`)
      //     will attempt to update the drag state _again_ and go through this
      //     branch.
      //
      // Since we only want the mouse movement to update the drag state once,
      // we attempt to stop it here.
      //
      // Since we don't have unique identifiers for each event, the best we
      // can hope for is the timestamps are the same.
      //
      // Note that in general, the mouse movement will be acked before the
      // dragging starts, so this should happen rarely.
      if (initial_state_) {
        CHECK(initial_state_->event);
        auto timestamp = initial_state_->event->TimeStamp();
        initial_state_ = nullptr;
        if (timestamp == event.TimeStamp()) {
          return false;
        }
      }
      UpdateDragging(host, std::make_unique<blink::WebMouseEvent>(event),
                     std::make_unique<FailSafe<DispatchMouseEventCallback>>(
                         std::move(callback)));
      return true;
    case blink::mojom::EventType::kMouseUp:
      EndDragging(&host, std::make_unique<blink::WebMouseEvent>(event),
                  std::make_unique<FailSafe<DispatchMouseEventCallback>>(
                      std::move(callback)));
      return true;
    default:
      break;
  }
  return false;
}

void InputHandler::DragController::EnsureDraggingEntered(
    RenderWidgetHostImpl& host,
    const blink::WebMouseEvent& event) {
  // If the host is the same, then we've already entered into the widget.
  if (drag_state_->host.get() == &host) {
    return;
  }
  // If we entered a widget previously, we need to leave it.
  if (drag_state_->host) {
    drag_state_->host->DragTargetDragLeave(drag_state_->pos, drag_state_->pos);
  }
  drag_state_->data.view_id = host.GetRoutingID();
  host.DragTargetDragEnter(drag_state_->data, drag_state_->pos,
                           drag_state_->pos, drag_state_->mask,
                           event.GetModifiers(), base::DoNothing());
  drag_state_->host = host.GetWeakPtr();
}

void InputHandler::DragController::StartDragging(
    const content::DropData& drop_data,
    blink::DragOperationsMask drag_operations_mask) {
  if (!initial_state_->host || !initial_state_->event) {
    CancelDragging(base::DoNothing());
    return;
  }

  drag_state_ = std::make_unique<DragState>(
      DragState{drop_data, drag_operations_mask, nullptr, gfx::PointF(), 0,
                base::DoNothing()});
  UpdateDragging(*initial_state_->host,
                 // Note we don't move it here. See
                 // InputHandler::DragController::HandleMouseEvent.
                 std::make_unique<blink::WebMouseEvent>(*initial_state_->event),
                 nullptr);
}

void InputHandler::DragController::CancelDragging(base::OnceClosure callback) {
  if (!drag_state_ || !drag_state_->host) {
    if (auto* view = handler_->GetRootView()) {
      view->GetRenderWidgetHost()->DragSourceSystemDragEnded();
    }
    std::move(callback).Run();
    return;
  }

  drag_state_->host->DragTargetDragLeave(drag_state_->pos, drag_state_->pos);
  drag_state_->host->DragSourceEndedAt(drag_state_->pos, drag_state_->pos,
                                       ui::mojom::DragOperation::kNone,
                                       std::move(callback));
}

void InputHandler::DragController::UpdateDragging(
    RenderWidgetHostImpl& host,
    std::unique_ptr<blink::WebMouseEvent> event,
    std::unique_ptr<FailSafe<DispatchMouseEventCallback>> callback) {
  ++drag_state_->updating;

  drag_state_->pos = event->PositionInWidget();

  EnsureDraggingEntered(host, *event);
  const int modifiers = event->GetModifiers();
  drag_state_->host->DragTargetDragOver(
      drag_state_->pos, drag_state_->pos, drag_state_->mask, modifiers,
      base::BindOnce(&InputHandler::DragController::DragUpdated,
                     weak_factory_.GetWeakPtr(), std::move(event),
                     std::move(callback)));
}

void InputHandler::DragController::DragUpdated(
    std::unique_ptr<blink::WebMouseEvent> event,
    std::unique_ptr<FailSafe<DispatchMouseEventCallback>> callback,
    ui::mojom::DragOperation operation,
    bool document_is_handling_drag) {
  if (!drag_state_) {
    // Dragging ended, perhaps due to a previous mouse up or a drag
    // cancellation.
    handler_->HandleMouseEvent(std::move(event), callback->release());
    return;
  }
  drag_state_->data.operation = operation;
  drag_state_->data.document_is_handling_drag = document_is_handling_drag;

  --drag_state_->updating;
  if (callback) {
    callback->sendSuccess();
  }

  if (drag_state_->updating == 0 && drag_state_->updated_callback) {
    std::move(drag_state_->updated_callback).Run();
  }
}

void InputHandler::DragController::EndDragging(
    RenderWidgetHostImpl* host_hint,
    std::unique_ptr<blink::WebMouseEvent> event,
    std::unique_ptr<FailSafe<DispatchMouseEventCallback>> callback) {
  if (!drag_state_) {
    // Dragging already ended.
    return;
  }
  if (drag_state_->updating > 0) {
    auto update_callback = base::BindOnce(
        &InputHandler::DragController::EndDragging, weak_factory_.GetWeakPtr(),
        nullptr, std::move(event), std::move(callback));
    // Chaining callbacks to ensure none get replaced.
    drag_state_->updated_callback =
        drag_state_->updated_callback ? std::move(drag_state_->updated_callback)
                                            .Then(std::move(update_callback))
                                      : std::move(update_callback);
    return;
  }
  if (host_hint) {
    EndDraggingWithRenderWidgetHostAtPoint(
        std::move(event), std::move(callback),
        host_hint->GetRenderWidgetHostViewBase()->GetWeakPtr(),
        drag_state_->pos);
    return;
  }
  handler_->web_contents_->GetRenderWidgetHostAtPointAsynchronously(
      handler_->GetRootView(), drag_state_->pos,
      base::BindOnce(
          &InputHandler::DragController::EndDraggingWithRenderWidgetHostAtPoint,
          weak_factory_.GetWeakPtr(), std::move(event), std::move(callback)));
}

void InputHandler::DragController::EndDraggingWithRenderWidgetHostAtPoint(
    std::unique_ptr<blink::WebMouseEvent> event,
    std::unique_ptr<FailSafe<DispatchMouseEventCallback>> callback,
    base::WeakPtr<RenderWidgetHostViewBase> view,
    std::optional<gfx::PointF> maybe_point) {
  if (!view || !maybe_point) {
    CancelDragging(
        base::BindOnce(&FailSafe<DispatchMouseEventCallback>::sendFailure,
                       std::move(callback), DispatchResponse::InternalError()));
    return;
  }
  if (!drag_state_) {
    // Dragging ended, perhaps due to a previous mouse up or a drag
    // cancellation.
    handler_->OnWidgetForDispatchMouseEvent(
        callback->release(), std::move(event), view, maybe_point);
    return;
  }
  auto* host = RenderWidgetHostImpl::From(view->GetRenderWidgetHost());
  auto point = *maybe_point;

  host->DragTargetDrop(drag_state_->data, point, point, event->GetModifiers(),
                       base::DoNothing());
  host->DragSourceEndedAt(
      point, point, drag_state_->data.operation,
      base::BindOnce(&FailSafe<DispatchMouseEventCallback>::sendSuccess,
                     std::move(callback)));
}

InputHandler::InputHandler(bool allow_file_access,
                           bool allow_sending_input_to_browser)
    : DevToolsDomainHandler(Input::Metainfo::domainName),
      drag_controller_(*this),
      allow_file_access_(allow_file_access),
      allow_sending_input_to_browser_(allow_sending_input_to_browser) {}

InputHandler::~InputHandler() = default;

// static
std::vector<InputHandler*> InputHandler::ForAgentHost(
    DevToolsAgentHostImpl* host) {
  return host->HandlersByName<InputHandler>(Input::Metainfo::domainName);
}

void InputHandler::SetRenderer(int process_host_id,
                               RenderFrameHostImpl* frame_host) {
  if (frame_host == host_)
    return;
  ClearInputState();

  auto* old_web_contents = WebContentsImpl::FromRenderFrameHostImpl(host_);
  host_ = frame_host;
  web_contents_ = WebContentsImpl::FromRenderFrameHostImpl(host_);

  if (ignore_input_events_ && old_web_contents != web_contents_) {
    if (web_contents_) {
      scoped_ignore_input_events_ =
          web_contents_->IgnoreInputEvents(std::nullopt);
    } else {
      scoped_ignore_input_events_.reset();
    }
  }
}

void InputHandler::Wire(UberDispatcher* dispatcher) {
  frontend_ = std::make_unique<Input::Frontend>(dispatcher->channel());
  Input::Dispatcher::wire(dispatcher, this);
}

Response InputHandler::Disable() {
  ClearInputState();
  scoped_ignore_input_events_.reset();
  ignore_input_events_ = false;
  pointer_ids_.clear();
  touch_points_.clear();
  return Response::Success();
}

void InputHandler::DispatchKeyEvent(
    const std::string& type,
    std::optional<int> modifiers,
    std::optional<double> timestamp,
    std::optional<std::string> text,
    std::optional<std::string> unmodified_text,
    std::optional<std::string> key_identifier,
    std::optional<std::string> code,
    std::optional<std::string> key,
    std::optional<int> windows_virtual_key_code,
    std::optional<int> native_virtual_key_code,
    std::optional<bool> auto_repeat,
    std::optional<bool> is_keypad,
    std::optional<bool> is_system_key,
    std::optional<int> location,
    std::unique_ptr<Array<std::string>> commands,
    std::unique_ptr<DispatchKeyEventCallback> callback) {
  blink::WebInputEvent::Type web_event_type;

  if (type == Input::DispatchKeyEvent::TypeEnum::KeyDown) {
    web_event_type = blink::WebInputEvent::Type::kKeyDown;
  } else if (type == Input::DispatchKeyEvent::TypeEnum::KeyUp) {
    web_event_type = blink::WebInputEvent::Type::kKeyUp;
  } else if (type == Input::DispatchKeyEvent::TypeEnum::Char) {
    web_event_type = blink::WebInputEvent::Type::kChar;
  } else if (type == Input::DispatchKeyEvent::TypeEnum::RawKeyDown) {
    web_event_type = blink::WebInputEvent::Type::kRawKeyDown;
  } else {
    callback->sendFailure(Response::InvalidParams(
        base::StringPrintf("Unexpected event type '%s'", type.c_str())));
    return;
  }

  input::NativeWebKeyboardEvent event(
      web_event_type,
      GetEventModifiers(modifiers.value_or(blink::WebInputEvent::kNoModifiers),
                        auto_repeat.value_or(false), is_keypad.value_or(false),
                        location.value_or(0), 0),
      GetEventTimeTicks(timestamp));

  if (!SetKeyboardEventText(event.text, std::move(text))) {
    callback->sendFailure(Response::InvalidParams("Invalid 'text' parameter"));
    return;
  }
  if (!SetKeyboardEventText(event.unmodified_text,
                            std::move(unmodified_text))) {
    callback->sendFailure(
        Response::InvalidParams("Invalid 'unmodifiedText' parameter"));
    return;
  }

  if (windows_virtual_key_code.has_value()) {
    event.windows_key_code = windows_virtual_key_code.value();
  }
  if (native_virtual_key_code.has_value()) {
    event.native_key_code = native_virtual_key_code.value();
  }
  if (is_system_key.has_value()) {
    event.is_system_key = is_system_key.value();
  }

  if (code.has_value()) {
    event.dom_code = static_cast<int>(
        ui::KeycodeConverter::CodeStringToDomCode(code.value()));
  }

  if (key.has_value()) {
    event.dom_key =
        static_cast<int>(ui::KeycodeConverter::KeyStringToDomKey(key.value()));
  }

  if (!host_ || !host_->GetRenderWidgetHost()) {
    callback->sendFailure(Response::InternalError());
    return;
  }

  RenderWidgetHostImpl* widget_host = host_->GetRenderWidgetHost();
  if (!host_->GetParent() && widget_host->delegate()) {
    RenderWidgetHostImpl* target_host =
        widget_host->delegate()->GetFocusedRenderWidgetHost(widget_host);
    if (target_host)
      widget_host = target_host;
  }

  // We do not pass events to browser if there is no native key event
  // due to Mac needing the actual os_event.
  if (event.native_key_code && allow_sending_input_to_browser_)
    event.os_event = NativeInputEventBuilder::CreateEvent(event);
  else
    event.skip_if_unhandled = true;

  EnsureInjector(widget_host)
      ->InjectKeyboardEvent(event, std::move(commands), std::move(callback));
}

void InputHandler::InsertText(const std::string& text,
                              std::unique_ptr<InsertTextCallback> callback) {
  std::u16string text16 = base::UTF8ToUTF16(text);
  if (!host_ || !host_->GetRenderWidgetHost()) {
    callback->sendFailure(Response::InternalError());
    return;
  }

  RenderWidgetHostImpl* widget_host = host_->GetRenderWidgetHost();
  if (!host_->GetParent() && widget_host->delegate()) {
    RenderWidgetHostImpl* target_host =
        widget_host->delegate()->GetFocusedRenderWidgetHost(widget_host);
    if (target_host)
      widget_host = target_host;
  }

  base::OnceClosure closure =
      base::BindOnce(&InsertTextCallback::sendSuccess, std::move(callback));

  widget_host->Focus();
  widget_host->GetWidgetInputHandler()->ImeCommitText(
      text16, std::vector<ui::ImeTextSpan>(), gfx::Range::InvalidRange(), 0,
      std::move(closure));
}

void InputHandler::ImeSetComposition(
    const std::string& text,
    int selection_start,
    int selection_end,
    std::optional<int> replacement_start,
    std::optional<int> replacement_end,
    std::unique_ptr<ImeSetCompositionCallback> callback) {
  std::u16string text16 = base::UTF8ToUTF16(text);
  if (!host_ || !host_->GetRenderWidgetHost() || !web_contents_) {
    callback->sendFailure(Response::InternalError());
    return;
  }
  // Currently no DevTools target for Prerender.
  if (host_->GetLifecycleState() ==
      RenderFrameHost::LifecycleState::kPrerendering) {
    NOTREACHED();
  }

  // |RenderFrameHostImpl::GetRenderWidgetHost| returns the RWHImpl of the
  // nearest local root of |host_|.
  RenderWidgetHostImpl* widget_host = host_->GetRenderWidgetHost();
  if (widget_host->delegate()) {
    RenderWidgetHostImpl* target_host =
        widget_host->delegate()->GetFocusedRenderWidgetHost(widget_host);
    if (target_host)
      widget_host = target_host;
  }

  // If replacement start and end are not specified, then the range is invalid,
  // so no replacing will be done.
  gfx::Range replacement_range = gfx::Range::InvalidRange();

  // Check if replacement_start and end parameters were passed in
  if (replacement_start.has_value()) {
    replacement_range.set_start(replacement_start.value());
    if (replacement_end.has_value()) {
      replacement_range.set_end(replacement_end.value());
    } else {
      callback->sendFailure(Response::InvalidParams(
          "Either both replacement start/end are specified or neither."));
      return;
    }
  }

  base::OnceClosure closure = base::BindOnce(
      &ImeSetCompositionCallback::sendSuccess, std::move(callback));

  widget_host->Focus();

  widget_host->GetWidgetInputHandler()->ImeSetComposition(
      text16, std::vector<ui::ImeTextSpan>(), replacement_range,
      selection_start, selection_end, std::move(closure));
}

void InputHandler::DispatchMouseEvent(
    const std::string& event_type,
    double x,
    double y,
    std::optional<int> modifiers,
    std::optional<double> timestamp,
    std::optional<std::string> button,
    std::optional<int> buttons,
    std::optional<int> click_count,
    std::optional<double> force,
    std::optional<double> tangential_pressure,
    std::optional<double> tilt_x,
    std::optional<double> tilt_y,
    std::optional<int> twist,
    std::optional<double> delta_x,
    std::optional<double> delta_y,
    std::optional<std::string> pointer_type,
    std::unique_ptr<DispatchMouseEventCallback> callback) {
  base::expected<std::unique_ptr<blink::WebMouseEvent>, protocol::Response>
      maybe_event = CreateWebMouseEvent(
          event_type, x, y, ScaleFactor(), std::move(modifiers),
          std::move(timestamp), std::move(button), std::move(buttons),
          std::move(click_count), std::move(force),
          std::move(tangential_pressure), std::move(tilt_x), std::move(tilt_y),
          std::move(twist), std::move(delta_x), std::move(delta_y),
          std::move(pointer_type));
  if (!maybe_event.has_value()) {
    callback->sendFailure(std::move(maybe_event).error());
    return;
  }

  HandleMouseEvent(std::move(maybe_event).value(), std::move(callback));
}

void InputHandler::HandleMouseEvent(
    std::unique_ptr<blink::WebMouseEvent> event,
    std::unique_ptr<DispatchMouseEventCallback> callback) {
  bool is_wheel_event =
      event->GetType() == blink::WebInputEvent::Type::kMouseWheel;

  RenderWidgetHostImpl* widget_host =
      host_ ? host_->GetRenderWidgetHost() : nullptr;
  if (!widget_host || !widget_host->delegate() ||
      !widget_host->delegate()->GetInputEventRouter() ||
      !widget_host->GetView()) {
    callback->sendFailure(Response::InternalError());
    return;
  }

  auto findWidgetAndDispatchEvent = base::BindOnce(
      [](base::WeakPtr<InputHandler> self,
         base::WeakPtr<RenderWidgetHostImpl> widget_host,
         std::unique_ptr<blink::WebMouseEvent> event,
         std::unique_ptr<DispatchMouseEventCallback> callback, bool success) {
        if (!self || !widget_host)
          return;
        gfx::PointF position = event->PositionInWidget();
        widget_host->delegate()
            ->GetRenderWidgetHostAtPointAsynchronously(
                widget_host->GetView(), position,
                base::BindOnce(&InputHandler::OnWidgetForDispatchMouseEvent,
                               self, std::move(callback), std::move(event)));
      },
      weak_factory_.GetWeakPtr(), widget_host->GetWeakPtr(), std::move(event),
      std::move(callback));
  // We make sure the compositor is up to date before sending a wheel event.
  // Otherwise it wont be picked up by newly added event listeners on the main
  // thread.
  if (is_wheel_event) {
    widget_host->InsertVisualStateCallback(
        std::move(findWidgetAndDispatchEvent));
  } else {
    std::move(findWidgetAndDispatchEvent).Run(true);
  }
}

void InputHandler::DispatchDragEvent(
    const std::string& event_type,
    double x,
    double y,
    std::unique_ptr<Input::DragData> data,
    std::optional<int> modifiers,
    std::unique_ptr<DispatchDragEventCallback> callback) {
  if (!allow_file_access_ && data->HasFiles()) {
    callback->sendFailure(Response::InvalidParams("Not allowed"));
    return;
  }

  RenderWidgetHostImpl* widget_host =
      host_ ? host_->GetRenderWidgetHost() : nullptr;
  if (!widget_host || !widget_host->delegate() ||
      !widget_host->delegate()->GetInputEventRouter() ||
      !widget_host->GetView()) {
    callback->sendFailure(Response::InternalError());
    return;
  }

  widget_host->delegate()
      ->GetRenderWidgetHostAtPointAsynchronously(
          widget_host->GetView(), CssPixelsToPointF(x, y, ScaleFactor()),
          base::BindOnce(&InputHandler::OnWidgetForDispatchDragEvent,
                         weak_factory_.GetWeakPtr(), event_type, x, y,
                         std::move(data), std::move(modifiers),
                         std::move(callback)));
}

void InputHandler::OnWidgetForDispatchDragEvent(
    const std::string& event_type,
    double x,
    double y,
    std::unique_ptr<Input::DragData> data,
    std::optional<int> modifiers,
    std::unique_ptr<DispatchDragEventCallback> callback,
    base::WeakPtr<RenderWidgetHostViewBase> target,
    std::optional<gfx::PointF> maybe_point) {
  if (!target || !maybe_point.has_value()) {
    callback->sendFailure(Response::InternalError());
    return;
  }
  auto point = *maybe_point;
  RenderWidgetHostImpl* widget_host =
      RenderWidgetHostImpl::From(target->GetRenderWidgetHost());
  auto mask =
      static_cast<blink::DragOperationsMask>(data->GetDragOperationsMask());
  std::unique_ptr<DropData> drop_data =
      std::make_unique<DropData>(ProtocolDragDataToDropData(std::move(data)));
  drop_data->view_id = widget_host->GetRoutingID();
  int event_modifiers =
      GetEventModifiers(modifiers.value_or(blink::WebInputEvent::kNoModifiers),
                        false, false, 0, 0);
  if (event_type == Input::DispatchDragEvent::TypeEnum::DragEnter) {
    widget_host->DragTargetDragEnter(
        *drop_data, point, point, mask, event_modifiers,
        base::BindOnce(
            [](std::unique_ptr<DispatchDragEventCallback> callback,
               ::ui::mojom::DragOperation operation,
               bool document_is_handling_drag) { callback->sendSuccess(); },
            std::move(callback)));
  } else if (event_type == Input::DispatchDragEvent::TypeEnum::DragOver) {
    widget_host->DragTargetDragOver(
        point, point, mask, event_modifiers,
        base::BindOnce(
            [](std::unique_ptr<DispatchDragEventCallback> callback,
               ::ui::mojom::DragOperation operation,
               bool document_is_handling_drag) { callback->sendSuccess(); },
            std::move(callback)));
  } else if (event_type == Input::DispatchDragEvent::TypeEnum::Drop) {
    widget_host->DragTargetDragOver(
        point, point, mask, event_modifiers,
        base::BindOnce(
            [](std::unique_ptr<DropData> drop_data, int event_modifiers,
               std::unique_ptr<DispatchDragEventCallback> callback,
               base::WeakPtr<RenderWidgetHostViewBase> target,
               gfx::PointF point, ui::mojom::DragOperation current_op,
               bool document_is_handling_drag) {
              if (!target) {
                callback->sendFailure(Response::InternalError());
                return;
              }
              drop_data->operation = current_op;
              drop_data->document_is_handling_drag = document_is_handling_drag;
              RenderWidgetHostImpl* widget_host =
                  RenderWidgetHostImpl::From(target->GetRenderWidgetHost());
              widget_host->DragTargetDrop(*drop_data, point, point,
                                          event_modifiers, base::DoNothing());
              widget_host->DragSourceSystemDragEnded();
              widget_host->DragSourceEndedAt(
                  point, point, current_op,
                  base::BindOnce(
                      [](std::unique_ptr<DispatchDragEventCallback> callback) {
                        callback->sendSuccess();
                      },
                      std::move(callback)));
            },
            std::move(drop_data), event_modifiers, std::move(callback),
            std::move(target), point));

  } else if (event_type == Input::DispatchDragEvent::TypeEnum::DragCancel) {
    widget_host->DragSourceSystemDragEnded();
    widget_host->DragSourceEndedAt(
        point, point, ui::mojom::DragOperation::kNone,
        base::BindOnce(
            [](std::unique_ptr<DispatchDragEventCallback> callback) {
              callback->sendSuccess();
            },
            std::move(callback)));
  } else {
    callback->sendFailure(Response::InvalidParams(
        base::StringPrintf("Unexpected event type '%s'", event_type.c_str())));
  }
}

float InputHandler::ScaleFactor() {
  DCHECK(web_contents_);
  // Browser zoom
  RenderWidgetHostImpl* widget_host_for_zoom_level =
      (host_ && host_->GetRenderWidgetHost())
          ? host_->GetRenderWidgetHost()
          : web_contents_->GetPrimaryMainFrame()->GetRenderWidgetHost();
  float scale_factor = blink::ZoomLevelToZoomFactor(
      web_contents_->GetPendingZoomLevel(widget_host_for_zoom_level));
  // CSS zoom applied to embedding element (e.g. <iframe>), if applicable.
  if (host_) {
    if (RenderWidgetHostImpl* widget_host = host_->GetRenderWidgetHost()) {
      if (auto* view = widget_host->GetView()) {
        scale_factor *= view->GetCSSZoomFactor();
      }
    }
  }
  // Pinch zoom
  // TODO(crbug.com/400860567): Investigate if this should also be
  // host_->GetPage() when `host_` is available.
  scale_factor *= web_contents_->GetPrimaryPage().GetPageScaleFactor();

  return scale_factor;
}

void InputHandler::StartDragging(const content::DropData& drop_data,
                                 const blink::mojom::DragData& drag_data,
                                 blink::DragOperationsMask drag_operations_mask,
                                 bool* intercepted) {
  // Only allow when Input.setInterceptDrags is disabled.
  if (!intercept_drags_) {
    // If `last_mouse_move_` exists, then CDP is the currently handling mouse
    // movement, so intercept dragging.
    if (drag_controller_.initial_state_) {
      drag_controller_.StartDragging(drop_data, drag_operations_mask);
      *intercepted = true;
    }
    return;
  }

  if (*intercepted) {
    return;
  }
  *intercepted = true;

  auto items =
      std::make_unique<protocol::Array<protocol::Input::DragDataItem>>();
  for (const auto& item : drag_data.items) {
    if (!item->is_string())
      continue;
    const auto& string_item = item->get_string();
    auto protocol_item =
        protocol::Input::DragDataItem::Create()
            .SetMimeType(string_item->string_type)
            .SetData(base::UTF16ToUTF8(string_item->string_data))
            .Build();
    if (string_item->base_url.has_value())
      protocol_item->SetBaseURL(string_item->base_url->spec());
    if (string_item->title.has_value())
      protocol_item->SetTitle(base::UTF16ToUTF8(string_item->title.value()));
    items->push_back(std::move(protocol_item));
  }
  frontend_->DragIntercepted(protocol::Input::DragData::Create()
                                 .SetDragOperationsMask(drag_operations_mask)
                                 .SetItems(std::move(items))
                                 .Build());
}

void InputHandler::DragEnded() {
  drag_controller_.drag_state_ = nullptr;
  drag_controller_.initial_state_ = nullptr;
}

Response InputHandler::SetInterceptDrags(bool enabled) {
  intercept_drags_ = enabled;
  return Response::Success();
}

void InputHandler::OnWidgetForDispatchMouseEvent(
    std::unique_ptr<DispatchMouseEventCallback> callback,
    std::unique_ptr<blink::WebMouseEvent> event,
    base::WeakPtr<RenderWidgetHostViewBase> target,
    std::optional<gfx::PointF> point) {
  if (!target || !point.has_value()) {
    callback->sendFailure(Response::InternalError());
    return;
  }
  event->SetPositionInWidget(*point);
  event->SetPositionInScreen(
      ConvertWidgetPointToScreenPoint(CHECK_DEREF(target.get()), *point));

  RenderWidgetHostImpl* widget_host =
      RenderWidgetHostImpl::From(target->GetRenderWidgetHost());
  if (event->GetType() == blink::WebInputEvent::Type::kMouseWheel) {
    EnsureInjector(widget_host)
        ->InjectWheelEvent(static_cast<blink::WebMouseWheelEvent*>(event.get()),
                           std::move(callback));
  } else {
    EnsureInjector(widget_host)->InjectMouseEvent(*event, std::move(callback));
  }
}

void InputHandler::DispatchTouchEvent(
    const std::string& event_type,
    std::unique_ptr<Array<Input::TouchPoint>> touch_points,
    std::optional<int> modifiers,
    std::optional<double> timestamp,
    std::unique_ptr<DispatchTouchEventCallback> callback) {
  if (base::FeatureList::IsEnabled(features::kSyntheticPointerActions)) {
    DispatchSyntheticPointerActionTouch(
        event_type, std::move(touch_points), std::move(modifiers),
        std::move(timestamp), std::move(callback));
    return;
  }

  DispatchWebTouchEvent(event_type, std::move(touch_points),
                        std::move(modifiers), std::move(timestamp),
                        std::move(callback));
}

void InputHandler::CancelDragging(
    std::unique_ptr<CancelDraggingCallback> callback) {
  if (!drag_controller_.IsDragging()) {
    callback->sendSuccess();
    return;
  }
  drag_controller_.CancelDragging(base::BindOnce(
      &FailSafe<CancelDraggingCallback>::sendSuccess,
      std::make_unique<FailSafe<CancelDraggingCallback>>(std::move(callback))));
}

void InputHandler::DispatchWebTouchEvent(
    const std::string& event_type,
    std::unique_ptr<Array<Input::TouchPoint>> touch_points,
    std::optional<int> modifiers,
    std::optional<double> timestamp,
    std::unique_ptr<DispatchTouchEventCallback> callback) {
  base::expected<std::vector<blink::WebTouchEvent>, protocol::Response>
      maybe_events = CreateWebTouchEvents(
          event_type, std::move(modifiers), std::move(timestamp), ScaleFactor(),
          touch_points_, std::move(touch_points));
  if (!maybe_events.has_value()) {
    callback->sendFailure(std::move(maybe_events).error());
    return;
  }

  std::vector<blink::WebTouchEvent> events = std::move(maybe_events).value();
  if (events.empty()) {
    callback->sendSuccess();
    return;
  }

  RenderWidgetHostImpl* widget_host =
      host_ ? host_->GetRenderWidgetHost() : nullptr;
  if (!widget_host || !widget_host->delegate() ||
      !widget_host->delegate()->GetInputEventRouter() ||
      !widget_host->GetView()) {
    callback->sendFailure(Response::InternalError());
    return;
  }

  // We make sure the compositor is up to date before
  // sending a touch event. Otherwise it wont be
  // picked up by newly added event listeners on the main thread.
  widget_host->InsertVisualStateCallback(base::BindOnce(
      [](base::WeakPtr<InputHandler> self,
         base::WeakPtr<RenderWidgetHostImpl> widget_host,
         std::vector<blink::WebTouchEvent> events,
         std::unique_ptr<DispatchTouchEventCallback> callback, bool success) {
        if (!self || !widget_host) {
          return;
        }
        gfx::PointF point(events[0].touches[0].PositionInWidget());
        widget_host->delegate()
            ->GetRenderWidgetHostAtPointAsynchronously(
                widget_host->GetView(), point,
                base::BindOnce(&InputHandler::OnWidgetForDispatchWebTouchEvent,
                               self, std::move(callback), std::move(events)));
      },
      weak_factory_.GetWeakPtr(), widget_host->GetWeakPtr(), std::move(events),
      std::move(callback)));
}

void InputHandler::OnWidgetForDispatchWebTouchEvent(
    std::unique_ptr<DispatchTouchEventCallback> callback,
    std::vector<blink::WebTouchEvent> events,
    base::WeakPtr<RenderWidgetHostViewBase> target,
    std::optional<gfx::PointF> transformed) {
  if (!target || !transformed.has_value()) {
    callback->sendFailure(Response::InternalError());
    return;
  }
  RenderWidgetHostImpl* widget_host =
      RenderWidgetHostImpl::From(target->GetRenderWidgetHost());

  gfx::PointF original(events[0].touches[0].PositionInWidget());
  gfx::Vector2dF delta = *transformed - original;
  for (auto& event : events) {
    event.dispatch_type =
        event.GetType() == blink::WebInputEvent::Type::kTouchCancel
            ? blink::WebInputEvent::DispatchType::kEventNonBlocking
            : blink::WebInputEvent::DispatchType::kBlocking;
    event.moved_beyond_slop_region = true;
    event.unique_touch_event_id = ui::GetNextTouchEventId();
    for (unsigned j = 0; j < event.touches_length; j++) {
      gfx::PointF point = event.touches[j].PositionInWidget();
      gfx::PointF position_in_widget(point.x() + delta.x(),
                                     point.y() + delta.y());
      event.touches[j].SetPositionInWidget(position_in_widget);
      event.touches[j].SetPositionInScreen(ConvertWidgetPointToScreenPoint(
          CHECK_DEREF(target.get()), position_in_widget));
    }
  }
  EnsureInjector(widget_host)->InjectTouchEvents(events, std::move(callback));
}

void InputHandler::DispatchSyntheticPointerActionTouch(
    const std::string& event_type,
    std::unique_ptr<Array<Input::TouchPoint>> touch_points,
    std::optional<int> modifiers,
    std::optional<double> timestamp,
    std::unique_ptr<DispatchTouchEventCallback> callback) {
  if (!host_ || !host_->GetRenderWidgetHost()) {
    callback->sendFailure(Response::InternalError());
    return;
  }

  SyntheticPointerActionParams::PointerActionType pointer_action_type =
      GetTouchPointerActionType(event_type);
  if (pointer_action_type ==
      SyntheticPointerActionParams::PointerActionType::NOT_INITIALIZED) {
    callback->sendFailure(Response::InvalidParams(
        base::StringPrintf("Unexpected event type '%s'", event_type.c_str())));
    return;
  }

  int event_modifiers =
      GetEventModifiers(modifiers.value_or(blink::WebInputEvent::kNoModifiers),
                        false, false, 0, 0);

  if ((pointer_action_type ==
           SyntheticPointerActionParams::PointerActionType::PRESS ||
       pointer_action_type ==
           SyntheticPointerActionParams::PointerActionType::MOVE) &&
      touch_points->empty()) {
    callback->sendFailure(Response::InvalidParams(
        "TouchStart and TouchMove must have at least one touch point."));
    return;
  }
  if ((pointer_action_type ==
           SyntheticPointerActionParams::PointerActionType::RELEASE ||
       pointer_action_type ==
           SyntheticPointerActionParams::PointerActionType::CANCEL) &&
      !touch_points->empty()) {
    callback->sendFailure(Response::InvalidParams(
        "TouchEnd and TouchCancel must not have any touch points."));
    return;
  }
  if (pointer_action_type ==
          SyntheticPointerActionParams::PointerActionType::PRESS &&
      !pointer_ids_.empty()) {
    callback->sendFailure(Response::InvalidParams(
        "Must have no prior active touch points to start a new touch."));
    return;
  }
  if (pointer_action_type !=
          SyntheticPointerActionParams::PointerActionType::PRESS &&
      pointer_ids_.empty()) {
    callback->sendFailure(Response::InvalidParams(
        "Must send a TouchStart first to start a new touch."));
    return;
  }

  content::mojom::GestureSourceType gesture_source_type =
      content::mojom::GestureSourceType::kTouchInput;
  SyntheticPointerActionListParams action_list_params;
  SyntheticPointerActionListParams::ParamList param_list;
  action_list_params.gesture_source_type = gesture_source_type;
  if (pointer_action_type ==
          SyntheticPointerActionParams::PointerActionType::RELEASE ||
      pointer_action_type ==
          SyntheticPointerActionParams::PointerActionType::CANCEL) {
    for (auto it = pointer_ids_.begin(); it != pointer_ids_.end();) {
      SyntheticPointerActionParams action_params =
          PrepareSyntheticPointerActionParams(pointer_action_type, *it, 0, 0,
                                              event_modifiers);
      param_list.push_back(action_params);
      it = pointer_ids_.erase(it);
    }
  }

  size_t with_id = 0;
  gfx::PointF original;
  std::set<int> current_pointer_ids;
  for (size_t i = 0; i < touch_points->size(); ++i) {
    Input::TouchPoint* point = (*touch_points)[i].get();
    int id = point->GetId(i);  // index |i| is default for the id.
    if (point->HasId())
      with_id++;

    SyntheticPointerActionParams::PointerActionType action_type =
        SyntheticPointerActionParams::PointerActionType::MOVE;
    if (!base::Contains(pointer_ids_, id)) {
      pointer_ids_.insert(id);
      action_type = SyntheticPointerActionParams::PointerActionType::PRESS;
    }
    SyntheticPointerActionParams action_params =
        PrepareSyntheticPointerActionParams(
            action_type, id, point->GetX(), point->GetY(), event_modifiers,
            point->GetRadiusX(1.0f), point->GetRadiusY(1.0f),
            point->GetRotationAngle(0.0f), point->GetForce(1.0f));
    param_list.push_back(action_params);
    original = gfx::PointF(point->GetX(), point->GetY());
    current_pointer_ids.insert(id);
  }
  if (with_id > 0 && with_id < touch_points->size()) {
    callback->sendFailure(Response::InvalidParams(
        "All or none of the provided TouchPoints must supply ids."));
    return;
  }

  if (pointer_action_type ==
          SyntheticPointerActionParams::PointerActionType::MOVE &&
      current_pointer_ids.size() < pointer_ids_.size()) {
    for (auto it = pointer_ids_.begin(); it != pointer_ids_.end();) {
      if (base::Contains(current_pointer_ids, *it)) {
        it++;
        continue;
      }
      SyntheticPointerActionParams action_params =
          PrepareSyntheticPointerActionParams(
              SyntheticPointerActionParams::PointerActionType::RELEASE, *it, 0,
              0, event_modifiers);
      param_list.push_back(action_params);
      it = pointer_ids_.erase(it);
    }
  }
  action_list_params.PushPointerActionParamsList(param_list);

  if (!synthetic_pointer_driver_) {
    synthetic_pointer_driver_ =
        SyntheticPointerDriver::Create(gesture_source_type, true);
  }
  std::unique_ptr<SyntheticPointerAction> synthetic_gesture =
      std::make_unique<SyntheticPointerAction>(action_list_params);
  synthetic_gesture->SetSyntheticPointerDriver(
      synthetic_pointer_driver_->AsWeakPtr());

  RenderWidgetHostViewBase* root_view = GetRootView();
  if (!root_view) {
    callback->sendFailure(Response::InternalError());
    return;
  }

  root_view->host()->QueueSyntheticGesture(
      std::move(synthetic_gesture),
      base::BindOnce(&DispatchPointerActionsResponse, std::move(callback)));
}

SyntheticPointerActionParams InputHandler::PrepareSyntheticPointerActionParams(
    SyntheticPointerActionParams::PointerActionType pointer_action_type,
    int id,
    double x,
    double y,
    int key_modifiers,
    float radius_x,
    float radius_y,
    float rotation_angle,
    float force) {
  SyntheticPointerActionParams action_params(pointer_action_type);
  action_params.set_pointer_id(id);
  const SyntheticPointerActionParams::Button button =
      SyntheticPointerActionParams::Button::NO_BUTTON;
  switch (pointer_action_type) {
    case SyntheticPointerActionParams::PointerActionType::PRESS:
      action_params.set_position(
          gfx::PointF(x * ScaleFactor(), y * ScaleFactor()));
      action_params.set_button(button);
      action_params.set_key_modifiers(key_modifiers);
      action_params.set_width(radius_x * 2.f);
      action_params.set_height(radius_y * 2.f);
      action_params.set_rotation_angle(rotation_angle);
      action_params.set_force(force);
      break;
    case SyntheticPointerActionParams::PointerActionType::MOVE:
      action_params.set_position(
          gfx::PointF(x * ScaleFactor(), y * ScaleFactor()));
      action_params.set_key_modifiers(key_modifiers);
      action_params.set_width(radius_x * 2.f);
      action_params.set_height(radius_y * 2.f);
      action_params.set_rotation_angle(rotation_angle);
      action_params.set_force(force);
      break;
    case SyntheticPointerActionParams::PointerActionType::RELEASE:
    case SyntheticPointerActionParams::PointerActionType::CANCEL:
      action_params.set_button(button);
      action_params.set_key_modifiers(key_modifiers);
      break;
    case SyntheticPointerActionParams::PointerActionType::LEAVE:
    case SyntheticPointerActionParams::PointerActionType::IDLE:
    case SyntheticPointerActionParams::PointerActionType::NOT_INITIALIZED:
      NOTREACHED();
  }
  return action_params;
}

Response InputHandler::EmulateTouchFromMouseEvent(
    const std::string& type,
    int x,
    int y,
    const std::string& button,
    std::optional<double> timestamp,
    std::optional<double> delta_x,
    std::optional<double> delta_y,
    std::optional<int> modifiers,
    std::optional<int> click_count) {
  blink::WebInputEvent::Type event_type;
  if (type == Input::EmulateTouchFromMouseEvent::TypeEnum::MouseWheel) {
    event_type = blink::WebInputEvent::Type::kMouseWheel;
    if (!delta_x.has_value() || !delta_y.has_value()) {
      return Response::InvalidParams(
          "'deltaX' and 'deltaY' are expected for mouseWheel event");
    }
  } else {
    event_type = GetMouseEventType(type);
    if (event_type == blink::WebInputEvent::Type::kUndefined) {
      return Response::InvalidParams(
          base::StringPrintf("Unexpected event type '%s'", type.c_str()));
    }
  }

  blink::WebPointerProperties::Button event_button =
      blink::WebPointerProperties::Button::kNoButton;
  int button_modifiers = 0;
  if (!GetMouseEventButton(button, &event_button, &button_modifiers))
    return Response::InvalidParams("Invalid mouse button");

  ui::WebScopedInputEvent event;
  blink::WebMouseWheelEvent* wheel_event = nullptr;
  blink::WebMouseEvent* mouse_event = nullptr;
  if (type == Input::EmulateTouchFromMouseEvent::TypeEnum::MouseWheel) {
    wheel_event = new blink::WebMouseWheelEvent(
        event_type,
        GetEventModifiers(
            modifiers.value_or(blink::WebInputEvent::kNoModifiers), false,
            false, 0, 0) |
            button_modifiers,
        GetEventTimeTicks(timestamp));
    mouse_event = wheel_event;
    event.reset(wheel_event);
    wheel_event->delta_x = static_cast<float>(delta_x.value());
    wheel_event->delta_y = static_cast<float>(delta_y.value());
    wheel_event->phase = blink::WebMouseWheelEvent::kPhaseBegan;
  } else {
    mouse_event = new blink::WebMouseEvent(
        event_type,
        GetEventModifiers(
            modifiers.value_or(blink::WebInputEvent::kNoModifiers), false,
            false, 0, 0) |
            button_modifiers,
        GetEventTimeTicks(timestamp));
    event.reset(mouse_event);
  }

  if (!host_ || !host_->GetRenderWidgetHost() || !host_->GetView()) {
    return Response::InternalError();
  }

  gfx::PointF position_in_widget(x, y);
  mouse_event->SetPositionInWidget(position_in_widget);
  mouse_event->button = event_button;
  mouse_event->SetPositionInScreen(ConvertWidgetPointToScreenPoint(
      CHECK_DEREF(host_->GetView()), position_in_widget));
  mouse_event->click_count = click_count.value_or(0);
  mouse_event->pointer_type = blink::WebPointerProperties::PointerType::kTouch;

  base::OnceCallback<void(bool)> forward_event_func;

  if (wheel_event) {
    forward_event_func = base::BindOnce(
        [](base::WeakPtr<InputHandler> self,
           base::WeakPtr<RenderWidgetHostImpl> widget_host,
           blink::WebMouseWheelEvent* event,
           ui::WebScopedInputEvent event_deleter, bool success) {
          if (!self || !widget_host)
            return;

          widget_host->ForwardWheelEvent(*event);
          // Send a synthetic wheel event with phaseEnded to finish scrolling.
          event->delta_x = 0;
          event->delta_y = 0;
          event->phase = blink::WebMouseWheelEvent::kPhaseEnded;
          event->dispatch_type =
              blink::WebInputEvent::DispatchType::kEventNonBlocking;
          widget_host->ForwardWheelEvent(*event);
        },
        weak_factory_.GetWeakPtr(), host_->GetRenderWidgetHost()->GetWeakPtr(),
        wheel_event, std::move(event));
  } else {
    forward_event_func = base::BindOnce(
        [](base::WeakPtr<InputHandler> self,
           base::WeakPtr<RenderWidgetHostImpl> widget_host,
           blink::WebMouseEvent* event, ui::WebScopedInputEvent event_deleter,
           bool success) {
          if (!self || !widget_host)
            return;
          widget_host->ForwardMouseEvent(*event);
        },
        weak_factory_.GetWeakPtr(), host_->GetRenderWidgetHost()->GetWeakPtr(),
        mouse_event, std::move(event));
  }
  // We make sure the compositor is up to date before sending a mouse event.
  // Otherwise it wont be picked up by newly added event listeners on the main
  // thread.
  host_->GetRenderWidgetHost()->InsertVisualStateCallback(
      std::move(forward_event_func));
  return Response::Success();
}

Response InputHandler::SetIgnoreInputEvents(bool ignore) {
  ignore_input_events_ = ignore;
  if (!ignore) {
    scoped_ignore_input_events_.reset();
  } else if (web_contents_) {
    scoped_ignore_input_events_ =
        web_contents_->IgnoreInputEvents(std::nullopt);
  }
  return Response::Success();
}

void InputHandler::SynthesizePinchGesture(
    double x,
    double y,
    double scale_factor,
    std::optional<int> relative_speed,
    std::optional<std::string> gesture_source_type,
    std::unique_ptr<SynthesizePinchGestureCallback> callback) {
  if (!host_ || !host_->GetRenderWidgetHost()) {
    callback->sendFailure(Response::InternalError());
    return;
  }

  SyntheticPinchGestureParams gesture_params;
  const int kDefaultRelativeSpeed = 800;

  gesture_params.from_devtools_debugger = true;
  gesture_params.scale_factor = scale_factor;
  gesture_params.anchor = CssPixelsToPointF(x, y, ScaleFactor());
  if (!PointIsWithinContents(gesture_params.anchor)) {
    callback->sendFailure(Response::InvalidParams("Position out of bounds"));
    return;
  }

  gesture_params.relative_pointer_speed_in_pixels_s =
      relative_speed.value_or(kDefaultRelativeSpeed);

  if (!StringToGestureSourceType(
      std::move(gesture_source_type),
      gesture_params.gesture_source_type)) {
    callback->sendFailure(
        Response::InvalidParams("Unknown gestureSourceType"));
    return;
  }

  RenderWidgetHostViewBase* root_view = GetRootView();
  if (!root_view) {
    callback->sendFailure(Response::InternalError());
    return;
  }

  root_view->host()->QueueSyntheticGesture(
      std::make_unique<SyntheticPinchGesture>(gesture_params),
      base::BindOnce(&SendSynthesizePinchGestureResponse, std::move(callback)));
}

void InputHandler::SynthesizeScrollGesture(
    double x,
    double y,
    std::optional<double> x_distance,
    std::optional<double> y_distance,
    std::optional<double> x_overscroll,
    std::optional<double> y_overscroll,
    std::optional<bool> prevent_fling,
    std::optional<int> speed,
    std::optional<std::string> gesture_source_type,
    std::optional<int> repeat_count,
    std::optional<int> repeat_delay_ms,
    std::optional<std::string> interaction_marker_name,
    std::unique_ptr<SynthesizeScrollGestureCallback> callback) {
  if (!host_ || !host_->GetRenderWidgetHost()) {
    callback->sendFailure(Response::InternalError());
    return;
  }

  SyntheticSmoothScrollGestureParams gesture_params;
  gesture_params.from_devtools_debugger = true;
  gesture_params.granularity = ui::ScrollGranularity::kScrollByPrecisePixel;

  gesture_params.anchor = CssPixelsToPointF(x, y, ScaleFactor());
  if (!PointIsWithinContents(gesture_params.anchor)) {
    callback->sendFailure(Response::InvalidParams("Position out of bounds"));
    return;
  }

  const bool kDefaultPreventFling = true;
  const int kDefaultSpeed = 800;
  gesture_params.prevent_fling = prevent_fling.value_or(kDefaultPreventFling);
  gesture_params.speed_in_pixels_s = speed.value_or(kDefaultSpeed);

  if (x_distance.has_value() || y_distance.has_value()) {
    gesture_params.distances.push_back(CssPixelsToVector2dF(
        x_distance.value_or(0), y_distance.value_or(0), ScaleFactor()));
  }

  if (x_overscroll.has_value() || y_overscroll.has_value()) {
    gesture_params.distances.push_back(CssPixelsToVector2dF(
        -x_overscroll.value_or(0), -y_overscroll.value_or(0), ScaleFactor()));
  }

  if (!StringToGestureSourceType(
      std::move(gesture_source_type),
      gesture_params.gesture_source_type)) {
    callback->sendFailure(
        Response::InvalidParams("Unknown gestureSourceType"));
    return;
  }

  SynthesizeRepeatingScroll(gesture_params, repeat_count.value_or(0),
                            base::Milliseconds(repeat_delay_ms.value_or(250)),
                            interaction_marker_name.value_or(""), ++last_id_,
                            std::move(callback));
}

void InputHandler::SynthesizeRepeatingScroll(
    SyntheticSmoothScrollGestureParams gesture_params,
    int repeat_count,
    base::TimeDelta repeat_delay,
    std::string interaction_marker_name,
    int id,
    std::unique_ptr<SynthesizeScrollGestureCallback> callback) {
  RenderWidgetHostViewBase* root_view = GetRootView();
  if (!root_view) {
    callback->sendFailure(Response::ServerError("Frame was detached"));
    return;
  }

  if (!interaction_marker_name.empty()) {
    // TODO(alexclarke): Can we move this elsewhere? It doesn't really fit here.
    TRACE_EVENT_COPY_NESTABLE_ASYNC_BEGIN0(
        "benchmark", interaction_marker_name.c_str(),
        TRACE_ID_WITH_SCOPE(interaction_marker_name.c_str(), id));
  }

  root_view->host()->QueueSyntheticGesture(
      std::make_unique<SyntheticSmoothScrollGesture>(gesture_params),
      base::BindOnce(&InputHandler::OnScrollFinished,
                     weak_factory_.GetWeakPtr(), gesture_params, repeat_count,
                     repeat_delay, interaction_marker_name, id,
                     std::move(callback)));
}

void InputHandler::OnScrollFinished(
    SyntheticSmoothScrollGestureParams gesture_params,
    int repeat_count,
    base::TimeDelta repeat_delay,
    std::string interaction_marker_name,
    int id,
    std::unique_ptr<SynthesizeScrollGestureCallback> callback,
    SyntheticGesture::Result result) {
  if (!interaction_marker_name.empty()) {
    TRACE_EVENT_COPY_NESTABLE_ASYNC_END0(
        "benchmark", interaction_marker_name.c_str(),
        TRACE_ID_WITH_SCOPE(interaction_marker_name.c_str(), id));
  }

  if (repeat_count > 0) {
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
        FROM_HERE,
        base::BindOnce(&InputHandler::SynthesizeRepeatingScroll,
                       weak_factory_.GetWeakPtr(), gesture_params,
                       repeat_count - 1, repeat_delay, interaction_marker_name,
                       id, std::move(callback)),
        repeat_delay);
  } else {
    SendSynthesizeScrollGestureResponse(std::move(callback), result);
  }
}

void InputHandler::SynthesizeTapGesture(
    double x,
    double y,
    std::optional<int> duration,
    std::optional<int> tap_count,
    std::optional<std::string> gesture_source_type,
    std::unique_ptr<SynthesizeTapGestureCallback> callback) {
  if (!host_ || !host_->GetRenderWidgetHost()) {
    callback->sendFailure(Response::InternalError());
    return;
  }

  SyntheticTapGestureParams gesture_params;
  const int kDefaultDuration = 50;
  const int kDefaultTapCount = 1;

  gesture_params.position = CssPixelsToPointF(x, y, ScaleFactor());
  gesture_params.from_devtools_debugger = true;
  if (!PointIsWithinContents(gesture_params.position)) {
    callback->sendFailure(Response::InvalidParams("Position out of bounds"));
    return;
  }

  gesture_params.duration_ms = duration.value_or(kDefaultDuration);

  if (!StringToGestureSourceType(
      std::move(gesture_source_type),
      gesture_params.gesture_source_type)) {
    callback->sendFailure(
        Response::InvalidParams("Unknown gestureSourceType"));
    return;
  }

  int count = tap_count.value_or(kDefaultTapCount);
  if (!count) {
    callback->sendSuccess();
    return;
  }

  RenderWidgetHostViewBase* root_view = GetRootView();
  if (!root_view) {
    callback->sendFailure(Response::InternalError());
    return;
  }

  TapGestureResponse* response =
      new TapGestureResponse(std::move(callback), count);
  for (int i = 0; i < count; i++) {
    root_view->host()->QueueSyntheticGesture(
        std::make_unique<SyntheticTapGesture>(gesture_params),
        base::BindOnce(&TapGestureResponse::OnGestureResult,
                       base::Unretained(response)));
  }
}

void InputHandler::ClearInputState() {
  while (!injectors_.empty())
    (*injectors_.begin())->Cleanup();
  // TODO(dgozman): cleanup touch callbacks as well?
  pointer_ids_.clear();
}

bool InputHandler::PointIsWithinContents(gfx::PointF point) const {
  gfx::Rect bounds = host_->GetView()->GetViewBounds();
  bounds -= bounds.OffsetFromOrigin();  // Translate the bounds to (0,0).
  return bounds.Contains(point.x(), point.y());
}

InputHandler::InputInjector* InputHandler::EnsureInjector(
    RenderWidgetHostImpl* widget_host) {
  for (auto& it : injectors_) {
    if (it->HasWidgetHost(widget_host))
      return it.get();
  }
  InputInjector* injector = new InputInjector(this, widget_host);
  injectors_.emplace(injector);
  return injector;
}

RenderWidgetHostViewBase* InputHandler::GetRootView() {
  if (!host_)
    return nullptr;

  RenderWidgetHostViewBase* view =
      static_cast<RenderWidgetHostViewBase*>(host_->GetView());
  if (!view)
    return nullptr;

  return view->GetRootView();
}

}  // namespace content::protocol