#include <atomic>
#include "content/browser/renderer_host/input/fling_controller.h"
#include "base/time/default_tick_clock.h"
#include "base/trace_event/trace_event.h"
#include "content/browser/renderer_host/input/gesture_event_queue.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/common/content_client.h"
#include "ui/events/base_event_utils.h"
#include "ui/events/gestures/blink/web_gesture_curve_impl.h"
#if BUILDFLAG(IS_OHOS)
#include "base/ohos/dynamic_frame_loss_monitor.h"
#include "base/ohos/ltpo/include/sliding_observer.h"
#include "base/ohos/sys_info_utils.h"
#include "content/browser/gpu/gpu_process_host.h"
#include "base/report_loss_frame.h"
#include "ohos_adapter_helper.h"
#endif
using blink::WebInputEvent;
using blink::WebGestureEvent;
using blink::WebInputEvent;
namespace {
constexpr base::TimeDelta kFrameDelta = base::Seconds(1.0 / 60.0);
constexpr base::TimeDelta kMaxMicrosecondsFromFlingTimestampToFirstProgress =
base::Seconds(2.0 / 60.0);
const float kMinInertialScrollDelta = 0.1f;
const char* kFlingTraceName = "FlingController::HandlingGestureFling";
const int SOC_PERF_WEB_SLIDE_SCROLL = 10097;
}
namespace content {
std::atomic<int> FlingController::instance_count_ = 0;
FlingController::Config::Config() {}
FlingController::FlingController(
FlingControllerEventSenderClient* event_sender_client,
FlingControllerSchedulerClient* scheduler_client,
const Config& config)
: event_sender_client_(event_sender_client),
scheduler_client_(scheduler_client),
touchpad_tap_suppression_controller_(
config.touchpad_tap_suppression_config),
touchscreen_tap_suppression_controller_(
config.touchscreen_tap_suppression_config),
clock_(base::DefaultTickClock::GetInstance()) {
DCHECK(event_sender_client);
DCHECK(scheduler_client);
}
#if BUILDFLAG(IS_OHOS)
FlingController::~FlingController() {
if (!fling_curve_) {
return;
}
LOG(DEBUG) << "stop web page fling";
auto frame_rate = base::ohos::SlidingObserver::GetInstance().StopFling();
if (auto* host = GpuProcessHost::Get()) {
if (auto* host_impl = host->gpu_host()) {
host_impl->StopMonitor();
if (frame_rate >= 0) {
host_impl->ReportSlidingFrameRate(frame_rate);
}
}
}
}
#else
FlingController::~FlingController() = default;
#endif
bool FlingController::ObserveAndFilterForTapSuppression(
const GestureEventWithLatencyInfo& gesture_event) {
switch (gesture_event.event.GetType()) {
case WebInputEvent::Type::kGestureFlingCancel:
DCHECK(fling_curve_);
if (gesture_event.event.SourceDevice() ==
blink::WebGestureDevice::kTouchscreen) {
touchscreen_tap_suppression_controller_
.GestureFlingCancelStoppedFling();
} else if (gesture_event.event.SourceDevice() ==
blink::WebGestureDevice::kTouchpad) {
touchpad_tap_suppression_controller_.GestureFlingCancelStoppedFling();
}
return false;
case WebInputEvent::Type::kGestureTapDown:
case WebInputEvent::Type::kGestureShowPress:
case WebInputEvent::Type::kGestureTapUnconfirmed:
case WebInputEvent::Type::kGestureTapCancel:
case WebInputEvent::Type::kGestureTap:
case WebInputEvent::Type::kGestureDoubleTap:
case WebInputEvent::Type::kGestureLongPress:
#ifdef OHOS_DRAG_DROP
case WebInputEvent::Type::kGestureDragLongPress:
#endif
case WebInputEvent::Type::kGestureLongTap:
case WebInputEvent::Type::kGestureTwoFingerTap:
if (gesture_event.event.SourceDevice() ==
blink::WebGestureDevice::kTouchscreen) {
return touchscreen_tap_suppression_controller_.FilterTapEvent(
gesture_event);
}
return false;
default:
return false;
}
}
void FlingController::DynamicFrameLossEvent(const std::string& sceneId, bool isStart)
{
event_sender_client_->DynamicFrameLossEvent(sceneId, isStart);
}
bool FlingController::ObserveAndMaybeConsumeGestureEvent(
const GestureEventWithLatencyInfo& gesture_event) {
TRACE_EVENT0("input", "FlingController::ObserveAndMaybeConsumeGestureEvent");
if (gesture_event.event.GetType() ==
WebInputEvent::Type::kGestureFlingCancel &&
!fling_curve_) {
TRACE_EVENT_INSTANT0("input", "NoActiveFling", TRACE_EVENT_SCOPE_THREAD);
return true;
}
LOG(DEBUG) << "FlingController::ObserveAndMaybeConsumeGestureEvent::instance_count_: " << instance_count_;
if (gesture_event.event.GetType() == WebInputEvent::Type::kGestureFlingCancel && fling_curve_) {
LOG(DEBUG) << "FlingController::ObserveAndMaybeConsumeGestureEvent::dvsyncSwitch=false";
if (auto* host = GpuProcessHost::Get()) {
if (auto* host_impl = host->gpu_host()) {
host_impl->SetIsFling(false);
TRACE_EVENT0("input", "ObserveAndMaybeConsumeGestureEvent::SetIsFling=false, reason=kGestureFlingCancel");
}
}
}
if (ObserveAndFilterForTapSuppression(gesture_event)) {
TRACE_EVENT_INSTANT0("input", "FilterTapSuppression",
TRACE_EVENT_SCOPE_THREAD);
return true;
}
if (gesture_event.event.GetType() ==
WebInputEvent::Type::kGestureScrollUpdate) {
last_seen_scroll_update_ = gesture_event.event.TimeStamp();
} else if (gesture_event.event.GetType() ==
WebInputEvent::Type::kGestureScrollEnd ||
gesture_event.event.GetType() ==
WebInputEvent::Type::kGestureScrollBegin) {
last_seen_scroll_update_ = base::TimeTicks();
}
fling_booster_.ObserveGestureEvent(gesture_event.event);
if (gesture_event.event.GetType() ==
WebInputEvent::Type::kGestureFlingStart) {
std::string fling_string = "WEB_LIST_FLING";
instance_count_++;
ProcessGestureFlingStart(gesture_event);
#if BUILDFLAG(IS_OHOS)
ReportLossFrame::GetInstance()->SetScrollState(ScrollMode::START);
OHOS::NWeb::OhosAdapterHelper::GetInstance()
.GetHiTraceAdapterInstance()
.StartAsyncTrace(fling_string, 0);
int socPerfId = base::ohos::IsPcDevice() ? SOC_PERF_WEB_SLIDE_SCROLL :
OHOS::NWeb::SocPerfClientAdapter::SOC_PERF_WEB_GESTURE_ID;
OHOS::NWeb::OhosAdapterHelper::GetInstance()
.CreateSocPerfClientAdapter()
->ApplySocPerfConfigByIdEx(socPerfId, true);
if (instance_count_ == 1) {
LOG(DEBUG) << "FlingController::ObserveAndMaybeConsumeGestureEvent::dvsyncSwitch=true";
if (auto* host = GpuProcessHost::Get()) {
if (auto* host_impl = host->gpu_host()) {
host_impl->SetIsFling(true);
TRACE_EVENT0("input", "ObserveAndMaybeConsumeGestureEvent::SetIsFling=true, reason=kGestureFlingStart");
}
}
}
LOG(DEBUG) << "start web page fling";
if (auto* host = GpuProcessHost::Get()) {
if (auto* host_impl = host->gpu_host()) {
host_impl->StartMonitor();
TRACE_EVENT0("input", "DynamicFrameLossEvent Start");
GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&FlingController::DynamicFrameLossEvent,
weak_ptr_factory_.GetWeakPtr(), fling_string, true));
}
}
base::ohos::SlidingObserver::GetInstance().StartFling();
#endif
return true;
}
if (gesture_event.event.GetType() ==
WebInputEvent::Type::kGestureFlingCancel) {
ProcessGestureFlingCancel(gesture_event);
return true;
}
return false;
}
void FlingController::ProcessGestureFlingStart(
const GestureEventWithLatencyInfo& gesture_event) {
if (gesture_event.event.SourceDevice() ==
blink::WebGestureDevice::kTouchpad &&
last_wheel_event_consumed_) {
return;
}
if (!UpdateCurrentFlingState(gesture_event.event))
return;
TRACE_EVENT_ASYNC_BEGIN2("input", kFlingTraceName, this, "vx",
current_fling_parameters_.velocity.x(), "vy",
current_fling_parameters_.velocity.y());
last_progress_time_ = base::TimeTicks();
if (scheduler_client_->NeedsBeginFrameForFlingProgress())
ScheduleFlingProgress();
else
ProgressFling(clock_->NowTicks());
}
void FlingController::ScheduleFlingProgress() {
scheduler_client_->ScheduleFlingProgress(weak_ptr_factory_.GetWeakPtr());
}
void FlingController::ProcessGestureFlingCancel(
const GestureEventWithLatencyInfo& gesture_event) {
DCHECK(fling_curve_);
EndCurrentFling(gesture_event.event.TimeStamp());
}
void FlingController::ProgressFling(base::TimeTicks current_time) {
if (!fling_curve_)
return;
TRACE_EVENT_ASYNC_STEP_INTO0("input", kFlingTraceName, this, "ProgressFling");
if (!first_fling_update_sent()) {
if (current_fling_parameters_.start_time.is_null()) {
current_fling_parameters_.start_time = current_time;
ScheduleFlingProgress();
return;
}
if (current_time >= current_fling_parameters_.start_time +
kMaxMicrosecondsFromFlingTimestampToFirstProgress) {
current_fling_parameters_.start_time = current_time - kFrameDelta;
}
}
if (current_time < last_progress_time_ ||
current_time <= current_fling_parameters_.start_time) {
ScheduleFlingProgress();
return;
}
gfx::Vector2dF delta_to_scroll;
bool fling_is_active = fling_curve_->Advance(
(current_time - current_fling_parameters_.start_time).InSecondsF(),
current_fling_parameters_.velocity, delta_to_scroll);
#if BUILDFLAG(IS_OHOS)
if ((current_time - current_fling_parameters_.start_time).InSecondsF() > 0) {
int32_t preferred_frame_rate = base::ohos::SlidingObserver::GetInstance().OnFlingUpdate(
current_fling_parameters_.velocity.x(),
current_fling_parameters_.velocity.y());
if (auto* host = content::GpuProcessHost::Get()) {
if (auto* host_impl = host->gpu_host()) {
if (preferred_frame_rate >= 0) {
host_impl->ReportSlidingFrameRate(preferred_frame_rate);
}
}
}
}
#endif
if (!fling_is_active && current_fling_parameters_.source_device !=
blink::WebGestureDevice::kSyntheticAutoscroll) {
fling_booster_.Reset();
EndCurrentFling(current_time);
return;
}
if (std::abs(delta_to_scroll.x()) > kMinInertialScrollDelta ||
std::abs(delta_to_scroll.y()) > kMinInertialScrollDelta) {
GenerateAndSendFlingProgressEvents(current_time, delta_to_scroll);
last_progress_time_ = current_time;
}
ScheduleFlingProgress();
}
void FlingController::StopFling() {
fling_booster_.Reset();
if (fling_curve_)
EndCurrentFling(clock_->NowTicks());
}
void FlingController::GenerateAndSendWheelEvents(
base::TimeTicks current_time,
const gfx::Vector2dF& delta,
blink::WebMouseWheelEvent::Phase phase) {
MouseWheelEventWithLatencyInfo synthetic_wheel(
WebInputEvent::Type::kMouseWheel, current_fling_parameters_.modifiers,
current_time, ui::LatencyInfo(ui::SourceEventType::WHEEL));
synthetic_wheel.event.delta_units =
ui::ScrollGranularity::kScrollByPrecisePixel;
synthetic_wheel.event.delta_x = delta.x();
synthetic_wheel.event.delta_y = delta.y();
synthetic_wheel.event.momentum_phase = phase;
synthetic_wheel.event.has_synthetic_phase = true;
synthetic_wheel.event.SetPositionInWidget(current_fling_parameters_.point);
synthetic_wheel.event.SetPositionInScreen(
current_fling_parameters_.global_point);
synthetic_wheel.event.dispatch_type =
WebInputEvent::DispatchType::kEventNonBlocking;
event_sender_client_->SendGeneratedWheelEvent(synthetic_wheel);
}
void FlingController::GenerateAndSendGestureScrollEvents(
base::TimeTicks current_time,
WebInputEvent::Type type,
const gfx::Vector2dF& delta ) {
GestureEventWithLatencyInfo synthetic_gesture(
type, current_fling_parameters_.modifiers, current_time,
ui::LatencyInfo(ui::SourceEventType::INERTIAL));
synthetic_gesture.event.SetPositionInWidget(current_fling_parameters_.point);
synthetic_gesture.event.SetPositionInScreen(
current_fling_parameters_.global_point);
synthetic_gesture.event.primary_pointer_type =
blink::WebPointerProperties::PointerType::kTouch;
synthetic_gesture.event.SetSourceDevice(
current_fling_parameters_.source_device);
if (type == WebInputEvent::Type::kGestureScrollUpdate) {
synthetic_gesture.event.data.scroll_update.delta_x = delta.x();
synthetic_gesture.event.data.scroll_update.delta_y = delta.y();
synthetic_gesture.event.data.scroll_update.inertial_phase =
WebGestureEvent::InertialPhaseState::kMomentum;
} else {
DCHECK_EQ(WebInputEvent::Type::kGestureScrollEnd, type);
synthetic_gesture.event.data.scroll_end.inertial_phase =
WebGestureEvent::InertialPhaseState::kMomentum;
synthetic_gesture.event.data.scroll_end.generated_by_fling_controller =
true;
}
event_sender_client_->SendGeneratedGestureScrollEvents(synthetic_gesture);
}
void FlingController::GenerateAndSendFlingProgressEvents(
base::TimeTicks current_time,
const gfx::Vector2dF& delta) {
switch (current_fling_parameters_.source_device) {
case blink::WebGestureDevice::kTouchpad: {
blink::WebMouseWheelEvent::Phase phase =
first_fling_update_sent() ? blink::WebMouseWheelEvent::kPhaseChanged
: blink::WebMouseWheelEvent::kPhaseBegan;
GenerateAndSendWheelEvents(current_time, delta, phase);
break;
}
case blink::WebGestureDevice::kTouchscreen:
case blink::WebGestureDevice::kSyntheticAutoscroll:
GenerateAndSendGestureScrollEvents(
current_time, WebInputEvent::Type::kGestureScrollUpdate, delta);
break;
case blink::WebGestureDevice::kUninitialized:
case blink::WebGestureDevice::kScrollbar:
NOTREACHED()
<< "Fling controller doesn't handle flings with source device:"
<< static_cast<int>(current_fling_parameters_.source_device);
}
fling_booster_.ObserveProgressFling(current_fling_parameters_.velocity);
}
void FlingController::GenerateAndSendFlingEndEvents(
base::TimeTicks current_time) {
switch (current_fling_parameters_.source_device) {
case blink::WebGestureDevice::kTouchpad:
GenerateAndSendWheelEvents(current_time, gfx::Vector2d(),
blink::WebMouseWheelEvent::kPhaseEnded);
break;
case blink::WebGestureDevice::kTouchscreen:
case blink::WebGestureDevice::kSyntheticAutoscroll:
GenerateAndSendGestureScrollEvents(
current_time, WebInputEvent::Type::kGestureScrollEnd);
break;
case blink::WebGestureDevice::kUninitialized:
case blink::WebGestureDevice::kScrollbar:
NOTREACHED()
<< "Fling controller doesn't handle flings with source device:"
<< static_cast<int>(current_fling_parameters_.source_device);
}
}
void FlingController::EndCurrentFling(base::TimeTicks current_time) {
instance_count_--;
last_progress_time_ = base::TimeTicks();
std::string fling_string = "WEB_LIST_FLING";
GenerateAndSendFlingEndEvents(current_time);
#if BUILDFLAG(IS_OHOS)
ReportLossFrame::GetInstance()->SetScrollState(ScrollMode::STOP);
ReportLossFrame::GetInstance()->Report();
OHOS::NWeb::OhosAdapterHelper::GetInstance().GetHiTraceAdapterInstance()
.FinishAsyncTrace(fling_string, 0);
LOG(DEBUG) << "FlingController::EndCurrentFling::dvsyncSwitch=false";
if (auto* host = GpuProcessHost::Get()) {
if (auto* host_impl = host->gpu_host()) {
host_impl->SetIsFling(false);
TRACE_EVENT0("input", "EndCurrentFling::SetIsFling=false, reason=EndCurrentFling");
}
}
LOG(DEBUG) << "stop web page fling";
auto frame_rate = base::ohos::SlidingObserver::GetInstance().StopFling();
if (auto* host = GpuProcessHost::Get()) {
if (auto* host_impl = host->gpu_host()) {
host_impl->StopMonitor();
if (frame_rate >= 0) {
host_impl->ReportSlidingFrameRate(frame_rate);
}
TRACE_EVENT0("input", "DynamicFrameLossEvent End");
GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&FlingController::DynamicFrameLossEvent,
weak_ptr_factory_.GetWeakPtr(), fling_string, false));
}
}
#endif
current_fling_parameters_ = ActiveFlingParameters();
if (fling_curve_) {
scheduler_client_->DidStopFlingingOnBrowser(weak_ptr_factory_.GetWeakPtr());
TRACE_EVENT_ASYNC_END0("input", kFlingTraceName, this);
}
fling_curve_.reset();
}
bool FlingController::UpdateCurrentFlingState(
const WebGestureEvent& fling_start_event) {
DCHECK_EQ(WebInputEvent::Type::kGestureFlingStart,
fling_start_event.GetType());
const gfx::Vector2dF velocity =
fling_booster_.GetVelocityForFlingStart(fling_start_event);
current_fling_parameters_.velocity = velocity;
current_fling_parameters_.point = fling_start_event.PositionInWidget();
current_fling_parameters_.global_point = fling_start_event.PositionInScreen();
current_fling_parameters_.modifiers = fling_start_event.GetModifiers();
current_fling_parameters_.source_device = fling_start_event.SourceDevice();
if (fling_start_event.SourceDevice() ==
blink::WebGestureDevice::kSyntheticAutoscroll ||
last_seen_scroll_update_.is_null()) {
current_fling_parameters_.start_time = fling_start_event.TimeStamp();
} else {
current_fling_parameters_.start_time = last_seen_scroll_update_;
}
if (velocity.IsZero() && fling_start_event.SourceDevice() !=
blink::WebGestureDevice::kSyntheticAutoscroll) {
fling_booster_.Reset();
EndCurrentFling(fling_start_event.TimeStamp());
return false;
}
gfx::Size root_widget_viewport_size =
event_sender_client_->GetRootWidgetViewportSize();
if (root_widget_viewport_size.IsEmpty()) {
fling_booster_.Reset();
EndCurrentFling(last_seen_scroll_update_);
return false;
}
gfx::Vector2dF velocity_from_gfs(
fling_start_event.data.fling_start.velocity_x,
fling_start_event.data.fling_start.velocity_y);
float max_velocity_from_gfs =
std::max(fabs(velocity_from_gfs.x()), fabs(velocity_from_gfs.y()));
float max_velocity = std::max(fabs(current_fling_parameters_.velocity.x()),
fabs(current_fling_parameters_.velocity.y()));
float boost_multiplier = max_velocity / max_velocity_from_gfs;
fling_curve_ = std::unique_ptr<blink::WebGestureCurve>(
ui::WebGestureCurveImpl::CreateFromDefaultPlatformCurve(
current_fling_parameters_.source_device,
current_fling_parameters_.velocity,
gfx::Vector2dF() , false ,
GetContentClient()->browser()->ShouldUseMobileFlingCurve(),
current_fling_parameters_.global_point, boost_multiplier,
root_widget_viewport_size));
return true;
}
gfx::Vector2dF FlingController::CurrentFlingVelocity() const {
return current_fling_parameters_.velocity;
}
TouchpadTapSuppressionController*
FlingController::GetTouchpadTapSuppressionController() {
return &touchpad_tap_suppression_controller_;
}
void FlingController::OnWheelEventAck(
const MouseWheelEventWithLatencyInfo& event,
blink::mojom::InputEventResultSource ack_source,
blink::mojom::InputEventResultState ack_result) {
last_wheel_event_consumed_ =
(ack_result == blink::mojom::InputEventResultState::kConsumed);
}
}