// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CONTENT_BROWSER_RENDERER_HOST_INPUT_MOUSE_WHEEL_PHASE_HANDLER_H_
#define CONTENT_BROWSER_RENDERER_HOST_INPUT_MOUSE_WHEEL_PHASE_HANDLER_H_

#include "base/memory/raw_ptr.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "content/browser/renderer_host/render_widget_host_delegate.h"
#include "content/common/content_export.h"
#include "third_party/blink/public/common/input/web_mouse_wheel_event.h"
#include "third_party/blink/public/mojom/input/input_event_result.mojom-shared.h"

namespace content {
class RenderWidgetHostViewBase;

// The duration after which a synthetic wheel with zero deltas and
// phase = |kPhaseEnded| will be sent after the last wheel event.
constexpr base::TimeDelta kDefaultMouseWheelLatchingTransaction =
    base::Milliseconds(500);

// Maximum allowed difference between coordinates of two mouse wheel events in
// the same scroll sequence.
const double kWheelLatchingSlopRegion = 10.0;

// On ChromeOS wheel events don't have phase information; However, whenever the
// user puts down their fingers on touchpad a GFC is received and at the end of
// touchpad scrolling when the user lifts their fingers a GFS is received. This
// enum tracks the current state of the touchpad scrolling by listening to GFC
// and GFS events.
enum TouchpadScrollPhaseState {
  // Scrolling with normal mouse wheels doesn't give any information about the
  // state of scrolling.
  TOUCHPAD_SCROLL_STATE_UNKNOWN = 0,
  // Shows that the user has put their fingers down and a scroll may start.
  TOUCHPAD_SCROLL_MAY_BEGIN,
  // Scrolling has started and the user hasn't lift their fingers, yet.
  TOUCHPAD_SCROLL_IN_PROGRESS,
};

enum class FirstScrollUpdateAckState {
  // Shows that the ACK for the first GSU event is not arrived yet.
  kNotArrived = 0,
  // Shows that the first GSU event is consumed.
  kConsumed,
  // Shows that the first GSU event is not consumed.
  kNotConsumed,
};

// The MouseWheelPhaseHandler is responsible for adding the proper phase to
// wheel events. Phase information is necessary for wheel scrolling since it
// shows the start and end of a scrolling sequence.
class CONTENT_EXPORT MouseWheelPhaseHandler {
 public:
  MouseWheelPhaseHandler(RenderWidgetHostViewBase* const host_view);

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

  ~MouseWheelPhaseHandler() {}

  void AddPhaseIfNeededAndScheduleEndEvent(
      blink::WebMouseWheelEvent& mouse_wheel_event,
      bool should_route_event,
      bool is_fling_capable);
  void DispatchPendingWheelEndEvent();
  void IgnorePendingWheelEndEvent();
  void ResetTouchpadScrollSequence();
  void SendWheelEndForTouchpadScrollingIfNeeded(bool should_route_event);
  void TouchpadScrollingMayBegin();

  // Used to set the timer timeout for testing.
  void set_mouse_wheel_end_dispatch_timeout(base::TimeDelta timeout) {
    mouse_wheel_end_dispatch_timeout_ = timeout;
  }

  bool HasPendingWheelEndEvent() const {
    return mouse_wheel_end_dispatch_timer_.IsRunning();
  }
  void GestureEventAck(const blink::WebGestureEvent& event,
                       blink::mojom::InputEventResultState ack_result);

  // Used to verify the correctness of touchpad_scroll_phase_state_'s value in
  // testing.
  TouchpadScrollPhaseState touchpad_scroll_phase_state_for_test() const {
    return touchpad_scroll_phase_state_;
  }

  // Used in testing for setting the max time to wait for momentum phase began
  // after a scroll phase end.
  void set_max_time_between_phase_ended_and_momentum_phase_began(
      base::TimeDelta timeout) {
    max_time_between_phase_ended_and_momentum_phase_began_ = timeout;
  }

  // Used get the max time to wait for a momentum scroll to begin.
  const base::TimeDelta
  max_time_between_phase_ended_and_momentum_phase_began() {
    return max_time_between_phase_ended_and_momentum_phase_began_;
  }

  void DidEnterBackForwardCache();

 private:
  void SendSyntheticWheelEventWithPhaseEnded(bool should_route_event);
  void ScheduleMouseWheelEndDispatching(bool should_route_event,
                                        const base::TimeDelta timeout);
  bool IsWithinSlopRegion(const blink::WebMouseWheelEvent& wheel_event) const;
  bool HasDifferentModifiers(
      const blink::WebMouseWheelEvent& wheel_event) const;
  bool ShouldBreakLatchingDueToDirectionChange(
      const blink::WebMouseWheelEvent& wheel_event) const;

  const raw_ptr<RenderWidgetHostViewBase> host_view_;
  base::OneShotTimer mouse_wheel_end_dispatch_timer_;
  base::TimeDelta mouse_wheel_end_dispatch_timeout_;
  blink::WebMouseWheelEvent last_mouse_wheel_event_;
  TouchpadScrollPhaseState touchpad_scroll_phase_state_;
  // This is used to break the timer based latching when the difference between
  // the locations of the first wheel event and the current wheel event is
  // larger than some threshold. The variable value is only valid while the
  // dispatch timer is running.
  gfx::Vector2dF first_wheel_location_;

  // This is used to break the timer based latching when the new wheel event has
  // different modifiers or when it is in a different direction from the
  // previous wheel events and the scrolling has been ignored.
  blink::WebMouseWheelEvent initial_wheel_event_;

  FirstScrollUpdateAckState first_scroll_update_ack_state_ =
      FirstScrollUpdateAckState::kNotArrived;

  // Maximum time that the phase handler waits for arrival of a wheel event with
  // momentum_phase = kPhaseBegan before sending its previous wheel event with
  // phase = kPhaseEnded.
  base::TimeDelta max_time_between_phase_ended_and_momentum_phase_began_ =
      base::Milliseconds(100);
};

}  // namespace content

#endif  // CONTENT_BROWSER_RENDERER_HOST_INPUT_MOUSE_WHEEL_PHASE_HANDLER_H_