#include "content/browser/media/capture/mouse_cursor_overlay_controller.h"
#include <cmath>
#include <utility>
#include "base/check_op.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/task/sequenced_task_runner.h"
#include "base/time/default_tick_clock.h"
#include "base/time/time.h"
#include "third_party/blink/public/common/features_generated.h"
namespace content {
constexpr base::TimeDelta MouseCursorOverlayController::kIdleTimeout;
void MouseCursorOverlayController::Start(
std::unique_ptr<Overlay> overlay,
scoped_refptr<base::SequencedTaskRunner> task_runner) {
DCHECK_CALLED_ON_VALID_SEQUENCE(ui_sequence_checker_);
DCHECK(overlay);
DCHECK(task_runner);
Stop();
tick_clock_ = base::DefaultTickClock::GetInstance();
overlay_ = std::move(overlay);
overlay_task_runner_ = std::move(task_runner);
should_send_mouse_events_ =
base::FeatureList::IsEnabled(blink::features::kCapturedMouseEvents);
}
void MouseCursorOverlayController::Stop() {
DCHECK_CALLED_ON_VALID_SEQUENCE(ui_sequence_checker_);
if (overlay_) {
tick_clock_ = nullptr;
overlay_task_runner_->DeleteSoon(FROM_HERE, overlay_.release());
overlay_task_runner_ = nullptr;
should_send_mouse_events_ = false;
}
}
bool MouseCursorOverlayController::IsUserInteractingWithView() const {
return mouse_move_behavior() == kRecentlyMovedOrClicked;
}
base::WeakPtr<MouseCursorOverlayController>
MouseCursorOverlayController::GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
void MouseCursorOverlayController::SendMouseEvent() {
DCHECK_CALLED_ON_VALID_SEQUENCE(ui_sequence_checker_);
if (!overlay_task_runner_) {
return;
}
CHECK(overlay_);
if (!last_emitted_coordinates_.has_value() ||
last_observed_coordinates_ != *last_emitted_coordinates_) {
last_emitted_coordinates_ = last_observed_coordinates_;
last_emitted_coordinates_time_ = tick_clock_->NowTicks();
overlay_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&Overlay::OnCapturedMouseEvent,
base::Unretained(overlay_.get()),
last_observed_coordinates_));
}
}
void MouseCursorOverlayController::OnMouseCoordinatesUpdated(
const gfx::Point& coordinates) {
DCHECK_CALLED_ON_VALID_SEQUENCE(ui_sequence_checker_);
DCHECK(should_send_mouse_events_);
last_observed_coordinates_ = coordinates;
if (last_observed_coordinates_timer_.IsRunning()) {
return;
}
base::TimeDelta wait_time;
if (last_emitted_coordinates_) {
const base::TimeDelta time_since_last_event =
tick_clock_->NowTicks() - last_emitted_coordinates_time_;
wait_time = kMinWaitInterval - time_since_last_event;
}
if (wait_time.is_positive()) {
last_observed_coordinates_timer_.Start(
FROM_HERE, wait_time,
base::BindRepeating(&MouseCursorOverlayController::SendMouseEvent,
base::Unretained(this)));
} else {
SendMouseEvent();
}
}
void MouseCursorOverlayController::OnMouseMoved(const gfx::PointF& location) {
DCHECK_CALLED_ON_VALID_SEQUENCE(ui_sequence_checker_);
switch (mouse_move_behavior()) {
case kNotMoving:
set_mouse_move_behavior(kStartingToMove);
mouse_move_start_location_ = location;
mouse_activity_ended_timer_.Reset();
break;
case kStartingToMove:
if (std::abs(location.x() - mouse_move_start_location_.x()) >
kMinMovementPixels ||
std::abs(location.y() - mouse_move_start_location_.y()) >
kMinMovementPixels) {
set_mouse_move_behavior(kRecentlyMovedOrClicked);
mouse_activity_ended_timer_.Reset();
}
break;
case kRecentlyMovedOrClicked:
mouse_activity_ended_timer_.Reset();
break;
}
if (mouse_move_behavior() == kRecentlyMovedOrClicked) {
UpdateOverlay(location);
}
}
void MouseCursorOverlayController::OnMouseClicked(const gfx::PointF& location) {
DCHECK_CALLED_ON_VALID_SEQUENCE(ui_sequence_checker_);
mouse_activity_ended_timer_.Reset();
set_mouse_move_behavior(kRecentlyMovedOrClicked);
UpdateOverlay(location);
}
void MouseCursorOverlayController::OnMouseHasGoneIdle() {
DCHECK_CALLED_ON_VALID_SEQUENCE(ui_sequence_checker_);
mouse_activity_ended_timer_.Stop();
set_mouse_move_behavior(kNotMoving);
UpdateOverlay(gfx::PointF());
}
void MouseCursorOverlayController::UpdateOverlay(const gfx::PointF& location) {
DCHECK_CALLED_ON_VALID_SEQUENCE(ui_sequence_checker_);
if (!overlay_task_runner_) {
return;
}
CHECK(overlay_);
do {
if (mouse_move_behavior() != kRecentlyMovedOrClicked) {
break;
}
const gfx::NativeCursor cursor = GetCurrentCursorOrDefault();
const gfx::RectF relative_bounds =
ComputeRelativeBoundsForOverlay(cursor, location);
if (cursor == last_cursor_) {
if (bounds_ != relative_bounds) {
bounds_ = relative_bounds;
overlay_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&Overlay::SetBounds,
base::Unretained(overlay_.get()), bounds_));
}
return;
}
const SkBitmap cursor_image = GetCursorImage(cursor);
if (cursor_image.drawsNothing()) {
last_cursor_ = gfx::NativeCursor();
break;
}
last_cursor_ = cursor;
bounds_ = relative_bounds;
overlay_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&Overlay::SetImageAndBounds,
base::Unretained(overlay_.get()),
cursor_image, bounds_));
return;
} while (false);
if (!bounds_.IsEmpty()) {
bounds_ = gfx::RectF();
overlay_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&Overlay::SetBounds,
base::Unretained(overlay_.get()), bounds_));
}
}
void MouseCursorOverlayController::SetTickClockForTesting(
const base::TickClock* tick_clock) {
DCHECK_CALLED_ON_VALID_SEQUENCE(ui_sequence_checker_);
tick_clock_ = tick_clock;
}
}