blob: f0279ae722563f534321149e6ea628158168354e [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 "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) {
DCHECK_EQ(blink::WebInputEvent::kGesturePinchUpdate, pinch_event.GetType());
// 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;
// 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).
DCHECK_GT(pinch_event.data.pinch_update.scale, 0);
wheel_event.delta_y = 100.0f * log(pinch_event.data.pinch_update.scale);
wheel_event.has_precise_scrolling_deltas = true;
wheel_event.wheel_ticks_x = 0;
wheel_event.wheel_ticks_y = pinch_event.data.pinch_update.scale > 1 ? 1 : -1;
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)
: 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;
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 ||
pinch_event_awaiting_ack_->event.GetType() ==
blink::WebInputEvent::kGesturePinchEnd) {
// Wheel event listeners are given individual events with no phase
// information, so we don't need to do anything at the beginning or
// end of a pinch.
// TODO(565980): Consider sending the rest of the wheel events as
// non-blocking if the first wheel event of the pinch sequence is not
// consumed.
client_->OnGestureEventForPinchAck(*pinch_event_awaiting_ack_,
InputEventAckSource::BROWSER,
INPUT_EVENT_ACK_STATE_IGNORED);
pinch_event_awaiting_ack_.reset();
TryForwardNextEventToRenderer();
return;
}
const MouseWheelEventWithLatencyInfo synthetic_wheel(
CreateSyntheticWheelFromTouchpadPinchEvent(
pinch_event_awaiting_ack_->event),
pinch_event_awaiting_ack_->latency);
client_->SendMouseWheelEventForPinchImmediately(synthetic_wheel);
}
bool TouchpadPinchEventQueue::has_pending() const {
return !pinch_queue_.empty() || pinch_event_awaiting_ack_;
}
} // namespace content