chromium/content/browser/android/gesture_listener_manager.cc

// 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.

#include <memory>

#include "base/memory/raw_ptr.h"
#include "content/browser/android/gesture_listener_manager.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/browser/web_contents/web_contents_view_android.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/web_contents_observer.h"
#include "third_party/blink/public/common/input/web_input_event.h"
#include "ui/events/android/gesture_event_type.h"
#include "ui/gfx/geometry/size_f.h"

// Must come after all headers that specialize FromJniType() / ToJniType().
#include "content/public/android/content_jni_headers/GestureListenerManagerImpl_jni.h"

using blink::WebGestureEvent;
using blink::WebInputEvent;

using base::android::AttachCurrentThread;
using base::android::JavaParamRef;
using base::android::ScopedJavaLocalRef;

namespace content {

namespace {

int ToGestureEventType(WebInputEvent::Type type) {
  switch (type) {
    case WebInputEvent::Type::kGestureScrollBegin:
      return ui::GESTURE_EVENT_TYPE_SCROLL_START;
    case WebInputEvent::Type::kGestureScrollEnd:
      return ui::GESTURE_EVENT_TYPE_SCROLL_END;
    case WebInputEvent::Type::kGestureScrollUpdate:
      return ui::GESTURE_EVENT_TYPE_SCROLL_BY;
    case WebInputEvent::Type::kGestureFlingStart:
      return ui::GESTURE_EVENT_TYPE_FLING_START;
    case WebInputEvent::Type::kGestureFlingCancel:
      return ui::GESTURE_EVENT_TYPE_FLING_CANCEL;
    case WebInputEvent::Type::kGestureShowPress:
      return ui::GESTURE_EVENT_TYPE_SHOW_PRESS;
    case WebInputEvent::Type::kGestureTap:
      return ui::GESTURE_EVENT_TYPE_SINGLE_TAP_CONFIRMED;
    case WebInputEvent::Type::kGestureTapUnconfirmed:
      return ui::GESTURE_EVENT_TYPE_SINGLE_TAP_UNCONFIRMED;
    case WebInputEvent::Type::kGestureTapDown:
      return ui::GESTURE_EVENT_TYPE_TAP_DOWN;
    case WebInputEvent::Type::kGestureTapCancel:
      return ui::GESTURE_EVENT_TYPE_TAP_CANCEL;
    case WebInputEvent::Type::kGestureDoubleTap:
      return ui::GESTURE_EVENT_TYPE_DOUBLE_TAP;
    case WebInputEvent::Type::kGestureLongPress:
      return ui::GESTURE_EVENT_TYPE_LONG_PRESS;
    case WebInputEvent::Type::kGestureLongTap:
      return ui::GESTURE_EVENT_TYPE_LONG_TAP;
    case WebInputEvent::Type::kGesturePinchBegin:
      return ui::GESTURE_EVENT_TYPE_PINCH_BEGIN;
    case WebInputEvent::Type::kGesturePinchEnd:
      return ui::GESTURE_EVENT_TYPE_PINCH_END;
    case WebInputEvent::Type::kGesturePinchUpdate:
      return ui::GESTURE_EVENT_TYPE_PINCH_BY;
    case WebInputEvent::Type::kGestureTwoFingerTap:
    default:
      NOTREACHED_IN_MIGRATION()
          << "Invalid source gesture type: " << WebInputEvent::GetName(type);
      return -1;
  }
}

}  // namespace

// Reset scroll, hide popups on navigation finish/render process gone event.
class GestureListenerManager::ResetScrollObserver : public WebContentsObserver {
 public:
  ResetScrollObserver(WebContents* web_contents,
                      GestureListenerManager* manager);

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

  void PrimaryPageChanged(Page& page) override;
  void PrimaryMainFrameRenderProcessGone(
      base::TerminationStatus status) override;

 private:
  const raw_ptr<GestureListenerManager> manager_;
};

GestureListenerManager::ResetScrollObserver::ResetScrollObserver(
    WebContents* web_contents,
    GestureListenerManager* manager)
    : WebContentsObserver(web_contents), manager_(manager) {}

void GestureListenerManager::ResetScrollObserver::PrimaryPageChanged(Page&) {
  manager_->OnPrimaryPageChanged();
}

void GestureListenerManager::ResetScrollObserver::
    PrimaryMainFrameRenderProcessGone(base::TerminationStatus status) {
  manager_->OnRenderProcessGone();
}

GestureListenerManager::GestureListenerManager(JNIEnv* env,
                                               const JavaParamRef<jobject>& obj,
                                               WebContentsImpl* web_contents)
    : RenderWidgetHostConnector(web_contents),
      reset_scroll_observer_(new ResetScrollObserver(web_contents, this)),
      web_contents_(web_contents),
      java_ref_(env, obj) {}

GestureListenerManager::~GestureListenerManager() {
  JNIEnv* env = AttachCurrentThread();
  ScopedJavaLocalRef<jobject> j_obj = java_ref_.get(env);
  if (j_obj.is_null())
    return;
  Java_GestureListenerManagerImpl_onNativeDestroyed(env, j_obj);
}

void GestureListenerManager::ResetGestureDetection(
    JNIEnv* env,
    const JavaParamRef<jobject>& obj) {
  if (rwhva_)
    rwhva_->ResetGestureDetection();
}

void GestureListenerManager::SetDoubleTapSupportEnabled(
    JNIEnv* env,
    const JavaParamRef<jobject>& obj,
    jboolean enabled) {
  if (rwhva_)
    rwhva_->SetDoubleTapSupportEnabled(enabled);
}

void GestureListenerManager::SetMultiTouchZoomSupportEnabled(
    JNIEnv* env,
    const JavaParamRef<jobject>& obj,
    jboolean enabled) {
  if (rwhva_)
    rwhva_->SetMultiTouchZoomSupportEnabled(enabled);
}

void GestureListenerManager::SetRootScrollOffsetUpdateFrequency(
    JNIEnv* env,
    jint frequency) {
  auto new_frequency =
      static_cast<cc::mojom::RootScrollOffsetUpdateFrequency>(frequency);
  root_scroll_offset_update_frequency_ = new_frequency;
  if (rwhva_)
    rwhva_->UpdateRootScrollOffsetUpdateFrequency();
}

void GestureListenerManager::GestureEventAck(
    const blink::WebGestureEvent& event,
    blink::mojom::InputEventResultState ack_result) {
  // This is called to fix crash happening while WebContents is being
  // destroyed. See https://crbug.com/803244#c20
  if (web_contents_->IsBeingDestroyed())
    return;
  JNIEnv* env = AttachCurrentThread();
  ScopedJavaLocalRef<jobject> j_obj = java_ref_.get(env);
  if (j_obj.is_null())
    return;
  if (event.GetType() == blink::WebInputEvent::Type::kGestureScrollBegin) {
    Java_GestureListenerManagerImpl_onScrollBegin(
        env, j_obj, /*isDirectionUp*/ event.data.scroll_begin.delta_y_hint > 0);
    return;
  }
  bool consumed = ack_result == blink::mojom::InputEventResultState::kConsumed;
  if (event.GetType() == blink::WebInputEvent::Type::kGestureFlingStart &&
      consumed) {
    Java_GestureListenerManagerImpl_onFlingStart(
        env, j_obj, /*isDirectionUp*/ event.data.scroll_begin.delta_y_hint > 0);
    return;
  }

  Java_GestureListenerManagerImpl_onEventAck(
      env, j_obj, static_cast<int>(event.GetType()), consumed);
}

void GestureListenerManager::DidStopFlinging() {
  JNIEnv* env = AttachCurrentThread();
  ScopedJavaLocalRef<jobject> j_obj = java_ref_.get(env);
  if (j_obj.is_null())
    return;
  Java_GestureListenerManagerImpl_onFlingEnd(env, j_obj);
}

bool GestureListenerManager::FilterInputEvent(const WebInputEvent& event) {
  if (event.GetType() != WebInputEvent::Type::kGestureTap &&
      event.GetType() != WebInputEvent::Type::kGestureLongTap &&
      event.GetType() != WebInputEvent::Type::kGestureLongPress &&
      event.GetType() != WebInputEvent::Type::kMouseDown)
    return false;

  JNIEnv* env = AttachCurrentThread();
  ScopedJavaLocalRef<jobject> j_obj = java_ref_.get(env);
  if (j_obj.is_null())
    return false;

  web_contents_->GetNativeView()->RequestFocus();

  if (event.GetType() == WebInputEvent::Type::kMouseDown)
    return false;

  const WebGestureEvent& gesture = static_cast<const WebGestureEvent&>(event);
  int gesture_type = ToGestureEventType(event.GetType());
  float dip_scale = web_contents_->GetNativeView()->GetDipScale();
  return Java_GestureListenerManagerImpl_filterTapOrPressEvent(
      env, j_obj, gesture_type, gesture.PositionInWidget().x() * dip_scale,
      gesture.PositionInWidget().y() * dip_scale);
}

// All positions and sizes (except |top_shown_pix|) are in CSS pixels.
// Note that viewport_width/height is a best effort based.
void GestureListenerManager::UpdateScrollInfo(const gfx::PointF& scroll_offset,
                                              float page_scale_factor,
                                              const float min_page_scale,
                                              const float max_page_scale,
                                              const gfx::SizeF& content,
                                              const gfx::SizeF& viewport,
                                              const float content_offset,
                                              const float top_shown_pix,
                                              bool top_changed) {
  JNIEnv* env = AttachCurrentThread();
  ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
  if (obj.is_null())
    return;

  web_contents_->GetNativeView()->UpdateFrameInfo({viewport, content_offset});
  Java_GestureListenerManagerImpl_updateScrollInfo(
      env, obj, scroll_offset.x(), scroll_offset.y(), page_scale_factor,
      min_page_scale, max_page_scale, content.width(), content.height(),
      viewport.width(), viewport.height(), top_shown_pix, top_changed);
}

void GestureListenerManager::UpdateOnTouchDown() {
  JNIEnv* env = AttachCurrentThread();
  ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
  if (obj.is_null())
    return;

  Java_GestureListenerManagerImpl_updateOnTouchDown(env, obj);
}

void GestureListenerManager::OnRootScrollOffsetChanged(
    const gfx::PointF& root_scroll_offset) {
  JNIEnv* env = AttachCurrentThread();
  ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
  if (obj.is_null())
    return;

  Java_GestureListenerManagerImpl_onRootScrollOffsetChanged(
      env, obj, root_scroll_offset.x(), root_scroll_offset.y());
}

void GestureListenerManager::UpdateRenderProcessConnection(
    RenderWidgetHostViewAndroid* old_rwhva,
    RenderWidgetHostViewAndroid* new_rwhva) {
  if (old_rwhva)
    old_rwhva->SetGestureListenerManager(nullptr);
  if (new_rwhva)
    new_rwhva->SetGestureListenerManager(this);
  rwhva_ = new_rwhva;
}

void GestureListenerManager::OnPrimaryPageChanged() {
  ResetPopupsAndInput(false);
}

void GestureListenerManager::OnRenderProcessGone() {
  ResetPopupsAndInput(true);
}

bool GestureListenerManager::IsScrollInProgressForTesting() {
  JNIEnv* env = AttachCurrentThread();
  ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
  if (obj.is_null())
    return false;

  return Java_GestureListenerManagerImpl_isScrollInProgress(env, obj);
}

void GestureListenerManager::ResetPopupsAndInput(bool render_process_gone) {
  JNIEnv* env = AttachCurrentThread();
  ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
  if (obj.is_null())
    return;
  Java_GestureListenerManagerImpl_resetPopupsAndInput(env, obj,
                                                      render_process_gone);
}

jlong JNI_GestureListenerManagerImpl_Init(
    JNIEnv* env,
    const JavaParamRef<jobject>& obj,
    const JavaParamRef<jobject>& jweb_contents) {
  auto* web_contents = WebContents::FromJavaWebContents(jweb_contents);
  CHECK(web_contents) << "Should be created with a valid WebContents.";

  // Owns itself and gets destroyed when |WebContentsDestroyed| is called.
  auto* manager = new GestureListenerManager(
      env, obj, static_cast<WebContentsImpl*>(web_contents));
  manager->Initialize();
  return reinterpret_cast<intptr_t>(manager);
}

}  // namespace content