blob: ed62b51f89293a0fe4f90d89bf7b3cf005df47a6 [file] [log] [blame]
// Copyright 2018 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/renderer_host/input/touchpad_pinch_event_queue.h"
#include "base/trace_event/trace_event.h"
#include "content/public/common/content_features.h"
#include "third_party/blink/public/platform/web_mouse_wheel_event.h"
#include "ui/events/base_event_utils.h"
#include "ui/events/blink/blink_event_util.h"
#include "ui/latency/latency_info.h"
namespace content {
namespace {
blink::WebMouseWheelEvent CreateSyntheticWheelFromTouchpadPinchEvent(
const blink::WebGestureEvent& pinch_event,
blink::WebMouseWheelEvent::Phase phase,
bool cancelable) {
DCHECK(pinch_event.GetType() == blink::WebInputEvent::kGesturePinchUpdate ||
pinch_event.GetType() == blink::WebInputEvent::kGesturePinchEnd ||
pinch_event.GetType() == blink::WebInputEvent::kGestureDoubleTap);
float delta_y = 0.0f;
float wheel_ticks_y = 0.0f;
// The function to convert scales to deltaY values is designed to be
// compatible with websites existing use of wheel events, and with existing
// Windows trackpad behavior. In particular, we want:
// - deltas should accumulate via addition: f(s1*s2)==f(s1)+f(s2)
// - deltas should invert via negation: f(1/s) == -f(s)
// - zoom in should be positive: f(s) > 0 iff s > 1
// - magnitude roughly matches wheels: f(2) > 25 && f(2) < 100
// - a formula that's relatively easy to use from JavaScript
// Note that 'wheel' event deltaY values have their sign inverted. So to
// convert a wheel deltaY back to a scale use Math.exp(-deltaY/100).
if (pinch_event.GetType() == blink::WebInputEvent::kGesturePinchUpdate) {
DCHECK_GT(pinch_event.data.pinch_update.scale, 0);
delta_y = 100.0f * log(pinch_event.data.pinch_update.scale);
wheel_ticks_y = pinch_event.data.pinch_update.scale > 1 ? 1 : -1;
}
// For pinch gesture events, similar to ctrl+wheel zooming, allow content to
// prevent the browser from zooming by sending fake wheel events with the ctrl
// modifier set when we see trackpad pinch gestures. Ideally we'd someday get
// a platform 'pinch' event and send that instead.
blink::WebMouseWheelEvent wheel_event(
blink::WebInputEvent::kMouseWheel,
pinch_event.GetModifiers() | blink::WebInputEvent::kControlKey,
pinch_event.TimeStamp());
wheel_event.SetPositionInWidget(pinch_event.PositionInWidget());
wheel_event.SetPositionInScreen(pinch_event.PositionInScreen());
wheel_event.delta_x = 0;
wheel_event.delta_y = delta_y;
wheel_event.has_precise_scrolling_deltas = true;
wheel_event.phase = phase;
wheel_event.wheel_ticks_x = 0;
wheel_event.wheel_ticks_y = wheel_ticks_y;
if (cancelable)
wheel_event.dispatch_type = blink::WebInputEvent::kBlocking;
else
wheel_event.dispatch_type = blink::WebInputEvent::kEventNonBlocking;
return wheel_event;
}
} // namespace
// This is a single queued pinch event to which we add trace events.
class QueuedTouchpadPinchEvent : public GestureEventWithLatencyInfo {
public:
QueuedTouchpadPinchEvent(const GestureEventWithLatencyInfo& original_event)
: GestureEventWithLatencyInfo(original_event) {
TRACE_EVENT_ASYNC_BEGIN0("input", "TouchpadPinchEventQueue::QueueEvent",
this);
}
~QueuedTouchpadPinchEvent() {
TRACE_EVENT_ASYNC_END0("input", "TouchpadPinchEventQueue::QueueEvent",
this);
}
private:
DISALLOW_COPY_AND_ASSIGN(QueuedTouchpadPinchEvent);
};
TouchpadPinchEventQueue::TouchpadPinchEventQueue(
TouchpadPinchEventQueueClient* client)
: touchpad_async_pinch_events_(
base::FeatureList::IsEnabled(features::kTouchpadAsyncPinchEvents)),
client_(client) {
DCHECK(client_);
}
TouchpadPinchEventQueue::~TouchpadPinchEventQueue() = default;
void TouchpadPinchEventQueue::QueueEvent(
const GestureEventWithLatencyInfo& event) {
TRACE_EVENT0("input", "TouchpadPinchEventQueue::QueueEvent");
if (!pinch_queue_.empty()) {
QueuedTouchpadPinchEvent* last_event = pinch_queue_.back().get();
if (last_event->CanCoalesceWith(event)) {
// Terminate the LatencyInfo of the event before it gets coalesced away.
event.latency.Terminate();
last_event->CoalesceWith(event);
DCHECK_EQ(blink::WebInputEvent::kGesturePinchUpdate,
last_event->event.GetType());
TRACE_EVENT_INSTANT1("input",
"TouchpadPinchEventQueue::CoalescedPinchEvent",
TRACE_EVENT_SCOPE_THREAD, "scale",
last_event->event.data.pinch_update.scale);
return;
}
}
pinch_queue_.push_back(std::make_unique<QueuedTouchpadPinchEvent>(event));
TryForwardNextEventToRenderer();
}
void TouchpadPinchEventQueue::ProcessMouseWheelAck(
InputEventAckSource ack_source,
InputEventAckState ack_result,
const ui::LatencyInfo& latency_info) {
TRACE_EVENT0("input", "TouchpadPinchEventQueue::ProcessMouseWheelAck");
if (!pinch_event_awaiting_ack_)
return;
if (pinch_event_awaiting_ack_->event.GetType() ==
blink::WebInputEvent::kGesturePinchUpdate &&
!first_event_prevented_.has_value())
first_event_prevented_ = (ack_result == INPUT_EVENT_ACK_STATE_CONSUMED);
pinch_event_awaiting_ack_->latency.AddNewLatencyFrom(latency_info);
client_->OnGestureEventForPinchAck(*pinch_event_awaiting_ack_, ack_source,
ack_result);
pinch_event_awaiting_ack_.reset();
TryForwardNextEventToRenderer();
}
void TouchpadPinchEventQueue::TryForwardNextEventToRenderer() {
TRACE_EVENT0("input",
"TouchpadPinchEventQueue::TryForwardNextEventToRenderer");
if (pinch_queue_.empty() || pinch_event_awaiting_ack_)
return;
pinch_event_awaiting_ack_ = std::move(pinch_queue_.front());
pinch_queue_.pop_front();
if (pinch_event_awaiting_ack_->event.GetType() ==
blink::WebInputEvent::kGesturePinchBegin) {
client_->OnGestureEventForPinchAck(*pinch_event_awaiting_ack_,
InputEventAckSource::BROWSER,
INPUT_EVENT_ACK_STATE_IGNORED);
pinch_event_awaiting_ack_.reset();
TryForwardNextEventToRenderer();
return;
}
blink::WebMouseWheelEvent::Phase phase =
blink::WebMouseWheelEvent::kPhaseNone;
bool cancelable = true;
if (pinch_event_awaiting_ack_->event.GetType() ==
blink::WebInputEvent::kGesturePinchEnd) {
first_event_prevented_.reset();
phase = blink::WebMouseWheelEvent::kPhaseEnded;
cancelable = false;
} else if (pinch_event_awaiting_ack_->event.GetType() ==
blink::WebInputEvent::kGestureDoubleTap) {
phase = blink::WebMouseWheelEvent::kPhaseNone;
cancelable = true;
} else {
DCHECK_EQ(pinch_event_awaiting_ack_->event.GetType(),
blink::WebInputEvent::kGesturePinchUpdate);
// The first pinch update event should send a synthetic wheel with phase
// began.
if (!first_event_prevented_.has_value()) {
phase = blink::WebMouseWheelEvent::kPhaseBegan;
cancelable = true;
} else {
phase = blink::WebMouseWheelEvent::kPhaseChanged;
cancelable =
!touchpad_async_pinch_events_ || first_event_prevented_.value();
}
}
const MouseWheelEventWithLatencyInfo synthetic_wheel(
CreateSyntheticWheelFromTouchpadPinchEvent(
pinch_event_awaiting_ack_->event, phase, cancelable),
pinch_event_awaiting_ack_->latency);
client_->SendMouseWheelEventForPinchImmediately(synthetic_wheel);
}
bool TouchpadPinchEventQueue::has_pending() const {
return !pinch_queue_.empty() || pinch_event_awaiting_ack_;
}
} // namespace content