#include "chrome/renderer/actor/drag_and_release_tool.h"
#include "base/notimplemented.h"
#include "base/time/time.h"
#include "base/types/expected.h"
#include "chrome/common/actor/action_result.h"
#include "chrome/common/actor/actor_logging.h"
#include "chrome/common/actor/journal_details_builder.h"
#include "chrome/common/chrome_features.h"
#include "chrome/renderer/actor/tool_utils.h"
#include "content/public/renderer/render_frame.h"
#include "third_party/abseil-cpp/absl/strings/str_format.h"
#include "third_party/blink/public/common/input/web_coalesced_input_event.h"
#include "third_party/blink/public/common/input/web_input_event.h"
#include "third_party/blink/public/common/input/web_mouse_event.h"
#include "third_party/blink/public/platform/web_input_event_result.h"
#include "third_party/blink/public/web/web_frame_widget.h"
#include "third_party/blink/public/web/web_local_frame.h"
#include "third_party/blink/public/web/web_node.h"
#include "ui/events/base_event_utils.h"
#include "ui/gfx/geometry/point_f.h"
namespace actor {
namespace {
constexpr base::TimeDelta kInitialMoveDelay = base::Milliseconds(2);
constexpr base::TimeDelta kMoveDelay = base::Milliseconds(1);
constexpr float kDragIntervalPixels = 20;
}
using ::blink::WebCoalescedInputEvent;
using ::blink::WebInputEvent;
using ::blink::WebInputEventResult;
using ::blink::WebLocalFrame;
using ::blink::WebMouseEvent;
using ::blink::WebWidget;
using ::blink::mojom::EventType;
DragAndReleaseTool::DragAndReleaseTool(
content::RenderFrame& frame,
TaskId task_id,
Journal& journal,
mojom::DragAndReleaseActionPtr action,
mojom::ToolTargetPtr target,
mojom::ObservedToolTargetPtr observed_target)
: ToolBase(frame,
task_id,
journal,
std::move(target),
std::move(observed_target)),
action_(std::move(action)) {}
DragAndReleaseTool::~DragAndReleaseTool() = default;
void DragAndReleaseTool::Execute(ToolFinishedCallback callback) {
ValidatedResult validated_result = Validate();
if (!validated_result.has_value()) {
std::move(callback).Run(std::move(validated_result.error()));
return;
}
ResolvedTarget from_target = validated_result->from;
ResolvedTarget to_target = validated_result->to;
journal_->Log(task_id_, "DragAndReleaseTool::Execute",
JournalDetailsBuilder()
.Add("from", from_target.widget_point)
.Add("to", to_target.widget_point)
.Build());
WebWidget* widget = from_target.GetWidget(*this);
CHECK(widget);
if (!InjectMouseEvent(*widget, from_target.widget_point,
EventType::kMouseMove,
WebMouseEvent::Button::kNoButton)) {
std::move(callback).Run(
MakeResult(mojom::ActionResultCode::kDragAndReleaseFromMoveSuppressed));
return;
}
widget = from_target.GetWidget(*this);
if (!widget) {
std::move(callback).Run(
MakeResult(mojom::ActionResultCode::kFrameWentAway));
return;
}
if (!InjectMouseEvent(*widget, from_target.widget_point,
EventType::kMouseDown, WebMouseEvent::Button::kLeft)) {
std::move(callback).Run(
MakeResult(mojom::ActionResultCode::kDragAndReleaseDownSuppressed,
true));
return;
}
task_runner_ = base::SequencedTaskRunner::GetCurrentDefault();
task_runner_->PostDelayedTask(
FROM_HERE,
base::BindOnce(&DragAndReleaseTool::ProcessDrag,
weak_ptr_factory_.GetWeakPtr(), validated_result->from,
validated_result->to, std::move(callback)),
kInitialMoveDelay);
}
void DragAndReleaseTool::ProcessDrag(ResolvedTarget from,
ResolvedTarget to,
ToolFinishedCallback callback) {
WebWidget* widget = from.GetWidget(*this);
if (!widget) {
std::move(callback).Run(
MakeResult(mojom::ActionResultCode::kFrameWentAway));
return;
}
bool done = false;
gfx::Vector2dF drag_vector = to.widget_point - from.widget_point;
gfx::Vector2dF drag_direction = drag_vector;
drag_direction.Normalize();
drag_direction.Scale(kDragIntervalPixels);
gfx::PointF drag_point = from.widget_point;
drag_point.Offset(drag_direction.x(), drag_direction.y());
if (drag_direction.Length() >= drag_vector.Length()) {
done = true;
drag_point = to.widget_point;
}
if (!InjectMouseEvent(*widget, drag_point, EventType::kMouseMove,
WebMouseEvent::Button::kLeft)) {
std::move(callback).Run(
MakeResult(mojom::ActionResultCode::kDragAndReleaseToMoveSuppressed,
true));
return;
}
if (!done) {
task_runner_->PostDelayedTask(
FROM_HERE,
base::BindOnce(&DragAndReleaseTool::ProcessDrag,
weak_ptr_factory_.GetWeakPtr(),
ResolvedTarget{
.node = from.node,
.widget_point = drag_point,
.popup_handle = from.popup_handle,
},
to, std::move(callback)),
kMoveDelay);
} else {
task_runner_->PostDelayedTask(
FROM_HERE,
base::BindOnce(&DragAndReleaseTool::ProcessRelease,
weak_ptr_factory_.GetWeakPtr(), to, std::move(callback)),
kMoveDelay);
}
}
void DragAndReleaseTool::ProcessRelease(ResolvedTarget to_target,
ToolFinishedCallback callback) {
WebWidget* widget = to_target.GetWidget(*this);
if (!widget) {
std::move(callback).Run(
MakeResult(mojom::ActionResultCode::kFrameWentAway));
return;
}
if (!InjectMouseEvent(*widget, to_target.widget_point, EventType::kMouseUp,
WebMouseEvent::Button::kLeft)) {
std::move(callback).Run(
MakeResult(mojom::ActionResultCode::kDragAndReleaseUpSuppressed,
true));
return;
}
std::move(callback).Run(MakeOkResult());
}
std::string DragAndReleaseTool::DebugString() const {
return absl::StrFormat("DragAndReleaseTool[from-%s -> to-%s]",
ToDebugString(target_),
ToDebugString(action_->to_target));
}
DragAndReleaseTool::ValidatedResult DragAndReleaseTool::Validate() const {
CHECK(frame_->GetWebFrame());
CHECK(frame_->GetWebFrame()->FrameWidget());
const mojom::ToolTargetPtr& from_target = target_;
const mojom::ToolTargetPtr& to_target = action_->to_target;
CHECK(from_target);
CHECK(to_target);
ResolveResult resolved_from = ResolveTarget(*from_target);
ResolveResult resolved_to = ResolveTarget(*to_target);
if (!resolved_from.has_value()) {
return base::unexpected(std::move(resolved_from.error()));
}
if (!resolved_to.has_value()) {
return base::unexpected(std::move(resolved_to.error()));
}
if (resolved_from->GetWidget(*this) != resolved_to->GetWidget(*this)) {
static constexpr std::string_view kErrorMessage =
"Drag across widgets is not supported.";
NOTIMPLEMENTED() << kErrorMessage;
return base::unexpected(MakeResult(mojom::ActionResultCode::kNotImplemented,
false,
kErrorMessage));
}
return DragParams{resolved_from.value(), resolved_to.value()};
}
bool DragAndReleaseTool::InjectMouseEvent(WebWidget& widget,
gfx::PointF& position_in_widget,
WebInputEvent::Type type,
WebMouseEvent::Button button) {
WebMouseEvent mouse_event(type, WebInputEvent::kNoModifiers,
ui::EventTimeForNow());
mouse_event.SetPositionInWidget(position_in_widget);
mouse_event.button = button;
if (type == WebInputEvent::Type::kMouseDown ||
type == WebInputEvent::Type::kMouseUp) {
mouse_event.click_count = 1;
}
mouse_event.UpdateEventModifiersToMatchButton();
if (type == WebInputEvent::Type::kMouseMove) {
switch (button) {
case blink::WebMouseEvent::Button::kNoButton:
break;
case blink::WebMouseEvent::Button::kLeft:
mouse_event.SetModifiers(WebInputEvent::Modifiers::kLeftButtonDown);
break;
default:
NOTREACHED();
}
}
WebInputEventResult result = widget.HandleInputEvent(
WebCoalescedInputEvent(mouse_event, ui::LatencyInfo()));
return result != WebInputEventResult::kHandledSuppressed;
}
}