blob: 50adc497ce776e6e7b06d845e86f69edce83c2d6 [file] [log] [blame]
// Copyright 2021 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.
#include "content/browser/accessibility/touch_passthrough_manager.h"
#include "base/containers/contains.h"
#include "cc/trees/render_frame_metadata.h"
#include "content/browser/accessibility/browser_accessibility.h"
#include "content/browser/accessibility/browser_accessibility_manager.h"
#include "content/browser/renderer_host/input/synthetic_gesture_controller.h"
#include "content/browser/renderer_host/input/synthetic_gesture_target.h"
#include "content/browser/renderer_host/input/synthetic_pointer_action.h"
#include "content/browser/renderer_host/input/synthetic_touch_driver.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/renderer_host/render_frame_metadata_provider_impl.h"
#include "content/browser/renderer_host/render_widget_host_impl.h"
#include "content/browser/web_contents/web_contents_view.h"
#include "content/common/input/synthetic_pointer_action_list_params.h"
#include "content/common/input/synthetic_pointer_action_params.h"
#include "content/common/input/synthetic_tap_gesture_params.h"
#include "content/public/common/use_zoom_for_dsf_policy.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node_data.h"
namespace content {
// Touch events require an index, so that you can distinguish between multiple
// fingers in multi-finger gestures. Touch passthrough only simulates a single
// finger, so we always use the same touch index; it doesn't matter what
// number we pass here as long as it's always the same index.
constexpr int kSyntheticTouchIndex = 0;
TouchPassthroughManager::TouchPassthroughManager(RenderFrameHostImpl* rfh)
: rfh_(rfh) {}
TouchPassthroughManager::~TouchPassthroughManager() {
CancelTouchesAndDestroyTouchDriver();
}
void TouchPassthroughManager::OnTouchStart(
const gfx::Point& point_in_frame_pixels) {
if (is_passthrough_) {
// This shouldn't happen, but if we do ever get a touch start when
// we thought we were already passing through, we should reset our state.
NOTREACHED();
OnTouchEnd();
}
// Keep track of the current state.
is_touch_down_ = true;
// Perform a hit test to determine if this event is within a touch
// passthrough region. Use an incrementing ID for each hit test so
// that any callbacks that are received late can be ignored.
hit_test_id_++;
SendHitTest(point_in_frame_pixels,
base::BindOnce(&TouchPassthroughManager::OnHitTestResult,
weak_ptr_factory_.GetWeakPtr(), hit_test_id_,
base::TimeTicks::Now(), point_in_frame_pixels));
}
void TouchPassthroughManager::OnTouchMove(
const gfx::Point& point_in_frame_pixels) {
DCHECK(is_touch_down_);
if (is_passthrough_)
SimulateMove(point_in_frame_pixels, base::TimeTicks::Now());
}
void TouchPassthroughManager::OnTouchEnd() {
DCHECK(is_touch_down_);
is_touch_down_ = false;
if (is_passthrough_) {
SimulateRelease(base::TimeTicks::Now());
is_passthrough_ = false;
}
}
void TouchPassthroughManager::OnHitTestResult(
int hit_test_id,
base::TimeTicks event_time,
gfx::Point location,
BrowserAccessibilityManager* hit_manager,
int hit_node_id) {
// Ignore the result if it arrived too late to do something about it.
if (hit_test_id != hit_test_id_)
return;
// If it's not a touch passthrough node, we're done.
if (!IsTouchPassthroughNode(hit_manager, hit_node_id))
return;
// If touch is no longer down, we need to just esnd a tap.
if (!is_touch_down_) {
SimulatePress(location, event_time);
SimulateRelease(event_time);
return;
}
// Otherwise, send a press and set a flag to keep passing through
// events.
SimulatePress(location, event_time);
is_passthrough_ = true;
}
bool TouchPassthroughManager::IsTouchPassthroughNode(
BrowserAccessibilityManager* hit_manager,
int hit_node_id) {
// Given the result of a hit test, walk up the tree to determine if
// this node or an ancestor has the passthrough bit set.
if (!hit_manager)
return false;
BrowserAccessibility* hit_node = hit_manager->GetFromID(hit_node_id);
if (!hit_node)
return false;
while (hit_node) {
if (hit_node->GetData().GetBoolAttribute(
ax::mojom::BoolAttribute::kTouchPassthrough))
return true;
hit_node = hit_node->PlatformGetParent();
}
return false;
}
void TouchPassthroughManager::CreateTouchDriverIfNeeded() {
RenderWidgetHostImpl* rwh = rfh_->GetRenderWidgetHost();
std::unique_ptr<SyntheticGestureTarget> gesture_target_unique_ptr =
rwh->GetView()->CreateSyntheticGestureTarget();
gesture_target_ = gesture_target_unique_ptr.get();
gesture_controller_ = std::make_unique<SyntheticGestureController>(
rwh, std::move(gesture_target_unique_ptr));
touch_driver_ = std::make_unique<SyntheticTouchDriver>();
}
void TouchPassthroughManager::SendHitTest(
const gfx::Point& point_in_frame_pixels,
base::OnceCallback<void(BrowserAccessibilityManager* hit_manager,
int hit_node_id)> callback) {
rfh_->AccessibilityHitTest(point_in_frame_pixels, ax::mojom::Event::kNone, 0,
std::move(callback));
}
void TouchPassthroughManager::CancelTouchesAndDestroyTouchDriver() {
if (!touch_driver_)
return;
if (is_passthrough_) {
touch_driver_->Cancel(kSyntheticTouchIndex);
touch_driver_->DispatchEvent(gesture_target_, base::TimeTicks::Now());
}
is_touch_down_ = false;
touch_driver_.reset();
gesture_controller_.reset();
gesture_target_ = nullptr;
}
void TouchPassthroughManager::SimulatePress(const gfx::Point& point,
const base::TimeTicks& time) {
CreateTouchDriverIfNeeded();
gfx::Point css_point = ToCSSPoint(point);
touch_driver_->Press(css_point.x(), css_point.y(), kSyntheticTouchIndex);
touch_driver_->DispatchEvent(gesture_target_, time);
}
void TouchPassthroughManager::SimulateMove(const gfx::Point& point,
const base::TimeTicks& time) {
DCHECK(touch_driver_);
gfx::Point css_point = ToCSSPoint(point);
touch_driver_->Move(css_point.x(), css_point.y(), kSyntheticTouchIndex);
touch_driver_->DispatchEvent(gesture_target_, time);
}
void TouchPassthroughManager::SimulateRelease(const base::TimeTicks& time) {
DCHECK(touch_driver_);
touch_driver_->Release(kSyntheticTouchIndex);
touch_driver_->DispatchEvent(gesture_target_, time);
}
gfx::Point TouchPassthroughManager::ToCSSPoint(
gfx::Point point_in_frame_pixels) {
gfx::Point result = point_in_frame_pixels;
// Scale by the device scale factor.
float dsf = rfh_->AccessibilityGetDeviceScaleFactor();
if (IsUseZoomForDSFEnabled())
result = ScaleToRoundedPoint(result, 1.0 / dsf);
// Offset by the top controls height.
RenderWidgetHostImpl* rwhi = rfh_->GetRenderWidgetHost();
RenderFrameMetadataProviderImpl* render_frame_metadata_provider =
rwhi->render_frame_metadata_provider();
const cc::RenderFrameMetadata render_frame_metadata =
render_frame_metadata_provider->LastRenderFrameMetadata();
float offset = render_frame_metadata.top_controls_height / dsf;
result.Offset(0, offset);
return result;
}
} // namespace content