// Copyright 2015 The Chromium Authors. All rights reserved.
// 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_RENDER_WIDGET_HOST_INPUT_EVENT_ROUTER_H_
#define CONTENT_BROWSER_RENDERER_HOST_RENDER_WIDGET_HOST_INPUT_EVENT_ROUTER_H_

#include <stdint.h>

#include <map>
#include <unordered_map>
#include <vector>

#include "base/containers/hash_tables.h"
#include "base/gtest_prod_util.h"
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "build/build_config.h"
#include "components/viz/common/surfaces/surface_id.h"
#include "components/viz/host/hit_test/hit_test_query.h"
#include "components/viz/service/surfaces/surface_hittest_delegate.h"
#include "content/browser/renderer_host/event_with_latency_info.h"
#include "content/browser/renderer_host/input/touch_emulator_client.h"
#include "content/browser/renderer_host/render_widget_host_view_base_observer.h"
#include "content/browser/renderer_host/render_widget_targeter.h"
#include "content/common/content_export.h"
#include "content/public/common/input_event_ack_state.h"
#include "ui/gfx/geometry/vector2d_conversions.h"

struct FrameHostMsg_HittestData_Params;

#if defined(OS_WIN)
// Flaky on Windows. https://crbug.com/868308
#define MAYBE_TouchpadPinchOverOOPIF DISABLED_TouchpadPinchOverOOPIF
#else
#define MAYBE_TouchpadPinchOverOOPIF TouchpadPinchOverOOPIF
#endif  // OS_WIN

namespace blink {
class WebGestureEvent;
class WebInputEvent;
class WebMouseEvent;
class WebMouseWheelEvent;
class WebTouchEvent;
}

namespace gfx {
class Point;
class PointF;
}

namespace ui {
class LatencyInfo;
}

namespace content {

class RenderWidgetHostImpl;
class RenderWidgetHostView;
class RenderWidgetHostViewBase;
class RenderWidgetTargeter;
class TouchEmulator;

// Class owned by WebContentsImpl for the purpose of directing input events
// to the correct RenderWidgetHost on pages with multiple RenderWidgetHosts.
// It maintains a mapping of RenderWidgetHostViews to Surface IDs that they
// own. When an input event requires routing based on window coordinates,
// this class requests a Surface hit test from the provided |root_view| and
// forwards the event to the owning RWHV of the returned Surface ID.
class CONTENT_EXPORT RenderWidgetHostInputEventRouter
    : public RenderWidgetHostViewBaseObserver,
      public RenderWidgetTargeter::Delegate,
      public TouchEmulatorClient {
 public:
  RenderWidgetHostInputEventRouter();
  ~RenderWidgetHostInputEventRouter() final;

  void OnRenderWidgetHostViewBaseDestroyed(
      RenderWidgetHostViewBase* view) override;

  void RouteMouseEvent(RenderWidgetHostViewBase* root_view,
                       blink::WebMouseEvent* event,
                       const ui::LatencyInfo& latency);
  void RouteMouseWheelEvent(RenderWidgetHostViewBase* root_view,
                            blink::WebMouseWheelEvent* event,
                            const ui::LatencyInfo& latency);
  void RouteGestureEvent(RenderWidgetHostViewBase* root_view,
                         blink::WebGestureEvent* event,
                         const ui::LatencyInfo& latency);
  void OnHandledTouchStartOrFirstTouchMove(uint32_t unique_touch_event_id);
  void ProcessAckedTouchEvent(const TouchEventWithLatencyInfo& event,
                              InputEventAckState ack_result,
                              RenderWidgetHostViewBase* view);
  void RouteTouchEvent(RenderWidgetHostViewBase* root_view,
                       blink::WebTouchEvent *event,
                       const ui::LatencyInfo& latency);

  // |event| is in root coordinates.
  void BubbleScrollEvent(RenderWidgetHostViewBase* target_view,
                         const blink::WebGestureEvent& event,
                         const RenderWidgetHostViewBase* resending_view);
  void CancelScrollBubbling(RenderWidgetHostViewBase* target_view);

  void AddFrameSinkIdOwner(const viz::FrameSinkId& id,
                           RenderWidgetHostViewBase* owner);
  void RemoveFrameSinkIdOwner(const viz::FrameSinkId& id);

  bool is_registered(const viz::FrameSinkId& id) {
    return owner_map_.find(id) != owner_map_.end();
  }

  void OnHittestData(const FrameHostMsg_HittestData_Params& params);

  TouchEmulator* GetTouchEmulator();
  // Since GetTouchEmulator will lazily create a touch emulator, the following
  // accessor allows testing for its existence without causing it to be created.
  bool has_touch_emulator() const { return touch_emulator_.get(); }

  // Returns the RenderWidgetHostImpl inside the |root_view| at |point| where
  // |point| is with respect to |root_view|'s coordinates. If a RWHI is found,
  // the value of |transformed_point| is the coordinate of the point with
  // respect to the RWHI's coordinates. If |root_view| is nullptr, this method
  // will return nullptr and will not modify |transformed_point|.
  RenderWidgetHostImpl* GetRenderWidgetHostAtPoint(
      RenderWidgetHostViewBase* root_view,
      const gfx::PointF& point,
      gfx::PointF* transformed_point);

  // RenderWidgetTargeter::Delegate:
  RenderWidgetHostViewBase* FindViewFromFrameSinkId(
      const viz::FrameSinkId& frame_sink_id) const override;

  // Allows a target to claim or release capture of mouse events.
  void SetMouseCaptureTarget(RenderWidgetHostViewBase* target,
                             bool captures_dragging);

  std::vector<RenderWidgetHostView*> GetRenderWidgetHostViewsForTests() const;
  RenderWidgetTargeter* GetRenderWidgetTargeterForTests();

  // TouchEmulatorClient:
  void ForwardEmulatedGestureEvent(
      const blink::WebGestureEvent& event) override;
  void ForwardEmulatedTouchEvent(const blink::WebTouchEvent& event,
                                 RenderWidgetHostViewBase* target) override;
  void SetCursor(const WebCursor& cursor) override;
  void ShowContextMenuAtPoint(const gfx::Point& point,
                              const ui::MenuSourceType source_type) override;

 private:
  struct HittestData {
    bool ignored_for_hittest;
  };

  class HittestDelegate : public viz::SurfaceHittestDelegate {
   public:
    HittestDelegate(const std::unordered_map<viz::SurfaceId,
                                             HittestData,
                                             viz::SurfaceIdHash>& hittest_data);
    bool RejectHitTarget(const viz::SurfaceDrawQuad* surface_quad,
                         const gfx::Point& point_in_quad_space) override;
    bool AcceptHitTarget(const viz::SurfaceDrawQuad* surface_quad,
                         const gfx::Point& point_in_quad_space) override;

    const std::unordered_map<viz::SurfaceId, HittestData, viz::SurfaceIdHash>&
        hittest_data_;
  };

  using FrameSinkIdOwnerMap = std::unordered_map<viz::FrameSinkId,
                                                 RenderWidgetHostViewBase*,
                                                 viz::FrameSinkIdHash>;
  struct TargetData {
    RenderWidgetHostViewBase* target;
    gfx::Vector2dF delta;

    TargetData() : target(nullptr) {}
  };
  using TargetMap = std::map<uint32_t, TargetData>;

  void ClearAllObserverRegistrations();
  RenderWidgetTargetResult FindViewAtLocation(
      RenderWidgetHostViewBase* root_view,
      const gfx::PointF& point,
      const gfx::PointF& point_in_screen,
      viz::EventSource source,
      gfx::PointF* transformed_point) const;

  bool IsViewInMap(const RenderWidgetHostViewBase* view) const;
  void RouteTouchscreenGestureEvent(RenderWidgetHostViewBase* root_view,
                                    blink::WebGestureEvent* event,
                                    const ui::LatencyInfo& latency);

  RenderWidgetTargetResult FindTouchpadGestureEventTarget(
      RenderWidgetHostViewBase* root_view,
      const blink::WebGestureEvent& event) const;
  void RouteTouchpadGestureEvent(RenderWidgetHostViewBase* root_view,
                                 blink::WebGestureEvent* event,
                                 const ui::LatencyInfo& latency);
  void DispatchTouchpadGestureEvent(
      RenderWidgetHostViewBase* root_view,
      RenderWidgetHostViewBase* target,
      const blink::WebGestureEvent& touchpad_gesture_event,
      const ui::LatencyInfo& latency,
      const base::Optional<gfx::PointF>& target_location);

  // MouseMove/Enter/Leave events might need to be processed by multiple frames
  // in different processes for MouseEnter and MouseLeave event handlers to
  // properly fire. This method determines which RenderWidgetHostViews other
  // than the actual target require notification, and sends the appropriate
  // events to them. |event| should be in |root_view|'s coordinate space.
  void SendMouseEnterOrLeaveEvents(const blink::WebMouseEvent& event,
                                   RenderWidgetHostViewBase* target,
                                   RenderWidgetHostViewBase* root_view);

  // The following methods take a GestureScrollUpdate event and send a
  // GestureScrollBegin or GestureScrollEnd for wrapping it. This is needed
  // when GestureScrollUpdates are being forwarded for scroll bubbling.
  void SendGestureScrollBegin(RenderWidgetHostViewBase* view,
                              const blink::WebGestureEvent& event);
  void SendGestureScrollEnd(RenderWidgetHostViewBase* view,
                            const blink::WebGestureEvent& event);

  // Helper functions to implement RenderWidgetTargeter::Delegate functions.
  RenderWidgetTargetResult FindMouseEventTarget(
      RenderWidgetHostViewBase* root_view,
      const blink::WebMouseEvent& event) const;
  RenderWidgetTargetResult FindMouseWheelEventTarget(
      RenderWidgetHostViewBase* root_view,
      const blink::WebMouseWheelEvent& event) const;
  // Returns target for first TouchStart in a sequence, or a null target
  // otherwise.
  RenderWidgetTargetResult FindTouchEventTarget(
      RenderWidgetHostViewBase* root_view,
      const blink::WebTouchEvent& event);
  RenderWidgetTargetResult FindTouchscreenGestureEventTarget(
      RenderWidgetHostViewBase* root_view,
      const blink::WebGestureEvent& gesture_event);

  // |mouse_event| is in the coord-space of |root_view|.
  void DispatchMouseEvent(RenderWidgetHostViewBase* root_view,
                          RenderWidgetHostViewBase* target,
                          const blink::WebMouseEvent& mouse_event,
                          const ui::LatencyInfo& latency,
                          const base::Optional<gfx::PointF>& target_location);
  // |mouse_wheel_event| is in the coord-space of |root_view|.
  void DispatchMouseWheelEvent(
      RenderWidgetHostViewBase* root_view,
      RenderWidgetHostViewBase* target,
      const blink::WebMouseWheelEvent& mouse_wheel_event,
      const ui::LatencyInfo& latency,
      const base::Optional<gfx::PointF>& target_location);
  // Assumes |touch_event| has coordinates in the root view's coordinate space.
  void DispatchTouchEvent(RenderWidgetHostViewBase* root_view,
                          RenderWidgetHostViewBase* target,
                          const blink::WebTouchEvent& touch_event,
                          const ui::LatencyInfo& latency,
                          const base::Optional<gfx::PointF>& target_location);
  // Assumes |gesture_event| has coordinates in root view's coordinate space.
  void DispatchTouchscreenGestureEvent(
      RenderWidgetHostViewBase* root_view,
      RenderWidgetHostViewBase* target,
      const blink::WebGestureEvent& gesture_event,
      const ui::LatencyInfo& latency,
      const base::Optional<gfx::PointF>& target_location);

  // Transforms |point| from |root_view| coord space to |target| coord space.
  // Result is stored in |transformed_point|. Returns true if the transform
  // is successful, false otherwise.
  bool TransformPointToTargetCoordSpace(RenderWidgetHostViewBase* root_view,
                                        RenderWidgetHostViewBase* target,
                                        const gfx::PointF& point,
                                        gfx::PointF* transformed_point,
                                        viz::EventSource source) const;

  // TODO(828422): Remove once this issue no longer occurs.
  void ReportBubblingScrollToSameView(const blink::WebGestureEvent& event,
                                      const RenderWidgetHostViewBase* view);

  // RenderWidgetTargeter::Delegate:
  RenderWidgetTargetResult FindTargetSynchronously(
      RenderWidgetHostViewBase* root_view,
      const blink::WebInputEvent& event) override;
  void DispatchEventToTarget(
      RenderWidgetHostViewBase* root_view,
      RenderWidgetHostViewBase* target,
      const blink::WebInputEvent& event,
      const ui::LatencyInfo& latency,
      const base::Optional<gfx::PointF>& target_location) override;
  // Notify whether the events in the queue are being flushed due to touch ack
  // timeout, or the flushing has completed.
  void SetEventsBeingFlushed(bool events_being_flushed) override;

  FrameSinkIdOwnerMap owner_map_;
  TargetMap touchscreen_gesture_target_map_;
  TargetData touch_target_;
  TargetData touchscreen_gesture_target_;
  // The following variable is temporary, for diagnosis of
  // https://crbug.com/824774.
  bool touchscreen_gesture_target_in_map_;
  TargetData touchpad_gesture_target_;
  TargetData bubbling_gesture_scroll_target_;
  TargetData first_bubbling_scroll_target_;
  // Used to target wheel events for the duration of a scroll.
  TargetData wheel_target_;
  // Maintains the same target between mouse down and mouse up.
  TargetData mouse_capture_target_;

  // Tracked for the purpose of generating MouseEnter and MouseLeave events.
  RenderWidgetHostViewBase* last_mouse_move_target_;
  RenderWidgetHostViewBase* last_mouse_move_root_view_;

  // Tracked for the purpose of targeting subsequent fling cancel events.
  RenderWidgetHostViewBase* last_fling_start_target_ = nullptr;

  // Tracked for the purpose of providing a root_view when dispatching emulated
  // touch/gesture events.
  RenderWidgetHostViewBase* last_emulated_event_root_view_;
  float last_device_scale_factor_;

  int active_touches_;
  // Keep track of when we are between GesturePinchBegin and GesturePinchEnd
  // inclusive, as we need to route these events (and anything in between) to
  // the main frame.
  bool in_touchscreen_gesture_pinch_;
  bool gesture_pinch_did_send_scroll_begin_;
  std::unordered_map<viz::SurfaceId, HittestData, viz::SurfaceIdHash>
      hittest_data_;

  std::unique_ptr<RenderWidgetTargeter> event_targeter_;
  bool use_viz_hit_test_ = false;
  bool events_being_flushed_ = false;

  std::unique_ptr<TouchEmulator> touch_emulator_;

  base::WeakPtrFactory<RenderWidgetHostInputEventRouter> weak_ptr_factory_;

  DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostInputEventRouter);
  friend class RenderWidgetHostInputEventRouterTest;
  FRIEND_TEST_ALL_PREFIXES(SitePerProcessHitTestBrowserTest,
                           HitTestStaleDataDeletedView);
  FRIEND_TEST_ALL_PREFIXES(SitePerProcessHitTestBrowserTest,
                           InputEventRouterGestureTargetMapTest);
  FRIEND_TEST_ALL_PREFIXES(SitePerProcessHitTestBrowserTest,
                           InputEventRouterGesturePreventDefaultTargetMapTest);
  FRIEND_TEST_ALL_PREFIXES(SitePerProcessHitTestBrowserTest,
                           InputEventRouterTouchpadGestureTargetTest);
  FRIEND_TEST_ALL_PREFIXES(SitePerProcessHitTestBrowserTest,
                           MAYBE_TouchpadPinchOverOOPIF);
  FRIEND_TEST_ALL_PREFIXES(SitePerProcessMouseWheelHitTestBrowserTest,
                           InputEventRouterWheelTargetTest);
  FRIEND_TEST_ALL_PREFIXES(SitePerProcessMacBrowserTest,
                           InputEventRouterTouchpadGestureTargetTest);
};

}  // namespace content

#endif  // CONTENT_BROWSER_RENDERER_HOST_RENDER_WIDGET_HOST_INPUT_EVENT_ROUTER_H_
