Backed out 3 changesets (bug 1676771) for touchevents related failures CLOSED TREE

Backed out changeset 88d4a2ac2cfc (bug 1676771)
Backed out changeset 7aa9b58c7916 (bug 1676771)
Backed out changeset 7f5daf9a6889 (bug 1676771)
This commit is contained in:
Bogdan Tara 2020-11-17 22:15:14 +02:00
Родитель 7974baccc1
Коммит 43d33ea421
8 изменённых файлов: 33 добавлений и 1649 удалений

Просмотреть файл

@ -11,11 +11,6 @@
var touch_action_prefs = getPrefs("TOUCH_ACTION");
var touch_action_and_exact_touch_events_prefs = [
...touch_action_prefs,
["android.touch_resampling.enabled", false],
];
var subtests = [
// Simple test to exercise touch-action CSS property
{"file": "helper_touch_action.html", "prefs": touch_action_prefs},
@ -23,7 +18,7 @@ var subtests = [
{"file": "helper_touch_action_complex.html", "prefs": touch_action_prefs},
// Tests that touch-action CSS properties are handled in APZ without waiting
// on the main-thread, when possible
{"file": "helper_touch_action_regions.html", "prefs": touch_action_and_exact_touch_events_prefs},
{"file": "helper_touch_action_regions.html", "prefs": touch_action_prefs},
// Tests that touch-action inside zero-opacity items are respected
{"file": "helper_touch_action_zero_opacity_bug1500864.html", "prefs": touch_action_prefs},

Просмотреть файл

@ -230,12 +230,6 @@
type: bool
value: true
mirror: always
- name: android.touch_resampling.enabled
type: RelaxedAtomicBool
value: true
mirror: always
#endif
#---------------------------------------------------------------------------

Просмотреть файл

@ -1,380 +0,0 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "TouchResampler.h"
#include "nsAlgorithm.h"
/**
* TouchResampler implementation
*/
namespace mozilla {
namespace widget {
// The values below have been tested and found to be acceptable on a device
// with a display refresh rate of 60Hz and touch sampling rate of 100Hz.
// While their "ideal" values are dependent on the exact rates of each device,
// the values we've picked below should be somewhat robust across a variation of
// different rates. They mostly aim to avoid making predictions that are too far
// away (in terms of distance) from the finger, and to detect pauses in the
// finger motion without too much delay.
// Maximum time between two consecutive data points to consider resampling
// between them.
// Values between 1x and 5x of the touch sampling interval are reasonable.
static const double kTouchResampleWindowSize = 40.0;
// These next two values constrain the sampling timestamp.
// Our caller will usually adjust frame timestamps to be slightly in the past,
// for example by 5ms. This means that, during normal operation, we will
// maximally need to predict by [touch sampling rate] minus 5ms.
// So we would like kTouchResampleMaxPredictMs to satisfy the following:
// kTouchResampleMaxPredictMs + [frame time adjust] > [touch sampling rate]
static const double kTouchResampleMaxPredictMs = 8.0;
// This one is a protection against very outdated frame timestamps.
// Values larger than the touch sampling interval and less than 3x of the vsync
// interval are reasonable.
static const double kTouchResampleMaxBacksampleMs = 20.0;
// The maximum age of the most recent data point to consider resampling.
// Should be between 1x and 3x of the touch sampling interval.
static const double kTouchResampleOldTouchThresholdMs = 17.0;
uint64_t TouchResampler::ProcessEvent(MultiTouchInput&& aInput) {
mCurrentTouches.UpdateFromEvent(aInput);
uint64_t eventId = mNextEventId;
mNextEventId++;
if (aInput.mType == MultiTouchInput::MULTITOUCH_MOVE) {
// Touch move events are deferred until NotifyFrame.
mDeferredTouchMoveEvents.push({std::move(aInput), eventId});
} else {
// Non-move events are transferred to the outgoing queue unmodified.
// If there are pending touch move events, flush those out first, so that
// events are emitted in the right order.
FlushDeferredTouchMoveEventsUnresampled();
if (mInResampledState) {
// Return to a non-resampled state before emitting a non-move event.
ReturnToNonResampledState();
}
EmitEvent(std::move(aInput), eventId);
}
return eventId;
}
void TouchResampler::NotifyFrame(const TimeStamp& aTimeStamp) {
TimeStamp lastTouchTime = mCurrentTouches.LatestDataPointTime();
if (mDeferredTouchMoveEvents.empty() ||
(lastTouchTime &&
lastTouchTime < aTimeStamp - TimeDuration::FromMilliseconds(
kTouchResampleOldTouchThresholdMs))) {
// We haven't received a touch move event in a while, so the fingers must
// have stopped moving. Flush any old touch move events.
FlushDeferredTouchMoveEventsUnresampled();
if (mInResampledState) {
// Make sure we pause at the resting position that we actually observed,
// and not at a resampled position.
ReturnToNonResampledState();
}
// Clear touch location history so that we don't resample across a pause.
mCurrentTouches.ClearDataPoints();
return;
}
MOZ_RELEASE_ASSERT(lastTouchTime);
TimeStamp lowerBound = lastTouchTime - TimeDuration::FromMilliseconds(
kTouchResampleMaxBacksampleMs);
TimeStamp upperBound = lastTouchTime + TimeDuration::FromMilliseconds(
kTouchResampleMaxPredictMs);
TimeStamp sampleTime = clamped(aTimeStamp, lowerBound, upperBound);
if (mLastEmittedEventTime && sampleTime < mLastEmittedEventTime) {
// Keep emitted timestamps in order.
sampleTime = mLastEmittedEventTime;
}
// We have at least one pending touch move event. Pick one of the events from
// mDeferredTouchMoveEvents as the base event for the resampling adjustment.
// We want to produce an event stream whose timestamps are in the right order.
// As the base event, use the first event that's at or after sampleTime,
// unless there is no such event, in that case use the last one we have. We
// will set the timestamp on the resampled event to sampleTime later.
// Flush out any older events so that everything remains in the right order.
MultiTouchInput input;
uint64_t eventId;
while (true) {
MOZ_RELEASE_ASSERT(!mDeferredTouchMoveEvents.empty());
std::tie(input, eventId) = std::move(mDeferredTouchMoveEvents.front());
mDeferredTouchMoveEvents.pop();
if (mDeferredTouchMoveEvents.empty() || input.mTimeStamp >= sampleTime) {
break;
}
// Flush this event to the outgoing queue without resampling. What ends up
// on the screen will still be smooth because we will proceed to emit a
// resampled event before the paint for this frame starts.
PrependLeftoverHistoricalData(&input);
MOZ_RELEASE_ASSERT(input.mTimeStamp < sampleTime);
EmitEvent(std::move(input), eventId);
}
mOriginalOfResampledTouchMove = Nothing();
// Compute the resampled touch positions.
nsTArray<ScreenIntPoint> resampledPositions;
bool anyPositionDifferentFromOriginal = false;
for (const auto& touch : input.mTouches) {
ScreenIntPoint resampledPosition =
mCurrentTouches.ResampleTouchPositionAtTime(
touch.mIdentifier, touch.mScreenPoint, sampleTime);
if (resampledPosition != touch.mScreenPoint) {
anyPositionDifferentFromOriginal = true;
}
resampledPositions.AppendElement(resampledPosition);
}
if (anyPositionDifferentFromOriginal) {
// Store a copy of the original event, so that we can return to an
// non-resampled position later, if necessary.
mOriginalOfResampledTouchMove = Some(input);
// Add the original observed position to the historical data, as well as any
// leftover historical positions from the previous touch move event, and
// store the resampled values in the "final" position of the event.
PrependLeftoverHistoricalData(&input);
for (size_t i = 0; i < input.mTouches.Length(); i++) {
auto& touch = input.mTouches[i];
touch.mHistoricalData.AppendElement(SingleTouchData::HistoricalTouchData{
input.mTimeStamp,
touch.mScreenPoint,
touch.mLocalScreenPoint,
touch.mRadius,
touch.mRotationAngle,
touch.mForce,
});
// Remove any historical touch data that's in the future, compared to
// sampleTime. This data will be included by upcoming touch move
// events. This only happens if the frame timestamp can be older than the
// event timestamp, i.e. if interpolation occurs (rather than
// extrapolation).
auto futureDataStart = std::find_if(
touch.mHistoricalData.begin(), touch.mHistoricalData.end(),
[sampleTime](
const SingleTouchData::HistoricalTouchData& aHistoricalData) {
return aHistoricalData.mTimeStamp > sampleTime;
});
if (futureDataStart != touch.mHistoricalData.end()) {
nsTArray<SingleTouchData::HistoricalTouchData> futureData(
Span(touch.mHistoricalData).From(futureDataStart.GetIndex()));
touch.mHistoricalData.TruncateLength(futureDataStart.GetIndex());
mRemainingTouchData.insert({touch.mIdentifier, std::move(futureData)});
}
touch.mScreenPoint = resampledPositions[i];
}
input.mTimeStamp = sampleTime;
}
EmitEvent(std::move(input), eventId);
mInResampledState = anyPositionDifferentFromOriginal;
}
void TouchResampler::PrependLeftoverHistoricalData(MultiTouchInput* aInput) {
for (auto& touch : aInput->mTouches) {
auto leftoverData = mRemainingTouchData.find(touch.mIdentifier);
if (leftoverData != mRemainingTouchData.end()) {
nsTArray<SingleTouchData::HistoricalTouchData> data =
std::move(leftoverData->second);
mRemainingTouchData.erase(leftoverData);
touch.mHistoricalData.InsertElementsAt(0, data);
}
if (TimeStamp cutoffTime = mLastEmittedEventTime) {
// If we received historical touch data that was further in the past than
// the last resampled event, discard that data so that the touch data
// points are emitted in order.
touch.mHistoricalData.RemoveElementsBy(
[cutoffTime](const SingleTouchData::HistoricalTouchData& aTouchData) {
return aTouchData.mTimeStamp < cutoffTime;
});
}
}
mRemainingTouchData.clear();
}
void TouchResampler::FlushDeferredTouchMoveEventsUnresampled() {
while (!mDeferredTouchMoveEvents.empty()) {
MultiTouchInput input;
uint64_t eventId;
std::tie(input, eventId) = std::move(mDeferredTouchMoveEvents.front());
mDeferredTouchMoveEvents.pop();
PrependLeftoverHistoricalData(&input);
EmitEvent(std::move(input), eventId);
mInResampledState = false;
mOriginalOfResampledTouchMove = Nothing();
}
}
void TouchResampler::ReturnToNonResampledState() {
MOZ_RELEASE_ASSERT(mInResampledState);
MOZ_RELEASE_ASSERT(mDeferredTouchMoveEvents.empty(),
"Don't call this if there is a deferred touch move event. "
"We can return to the non-resampled state by sending that "
"event, rather than a copy of a previous event.");
// The last outgoing event was a resampled touch move event.
// Return to the non-resampled state, by sending a touch move event to
// "overwrite" any resampled positions with the original observed positions.
MultiTouchInput input = std::move(*mOriginalOfResampledTouchMove);
mOriginalOfResampledTouchMove = Nothing();
// For the event's timestamp, we want to backdate the correction as far as we
// can, while still preserving timestamp ordering. But we also don't want to
// backdate it to be older than it was originally.
if (mLastEmittedEventTime > input.mTimeStamp) {
input.mTimeStamp = mLastEmittedEventTime;
}
// Assemble the correct historical touch data for this event.
// We don't want to include data points that we've already sent out with the
// resampled event. And from the leftover data points, we only want those that
// don't duplicate the final time + position of this event.
for (auto& touch : input.mTouches) {
touch.mHistoricalData.Clear();
}
PrependLeftoverHistoricalData(&input);
for (auto& touch : input.mTouches) {
touch.mHistoricalData.RemoveElementsBy([&](const auto& histData) {
return histData.mTimeStamp >= input.mTimeStamp;
});
}
EmitExtraEvent(std::move(input));
mInResampledState = false;
}
void TouchResampler::TouchInfo::Update(const SingleTouchData& aTouch,
const TimeStamp& aEventTime) {
for (const auto& historicalData : aTouch.mHistoricalData) {
mBaseDataPoint = mLatestDataPoint;
mLatestDataPoint =
Some(DataPoint{historicalData.mTimeStamp, historicalData.mScreenPoint});
}
mBaseDataPoint = mLatestDataPoint;
mLatestDataPoint = Some(DataPoint{aEventTime, aTouch.mScreenPoint});
}
ScreenIntPoint TouchResampler::TouchInfo::ResampleAtTime(
const ScreenIntPoint& aLastObservedPosition, const TimeStamp& aTimeStamp) {
TimeStamp cutoff =
aTimeStamp - TimeDuration::FromMilliseconds(kTouchResampleWindowSize);
if (!mBaseDataPoint || !mLatestDataPoint ||
!(mBaseDataPoint->mTimeStamp < mLatestDataPoint->mTimeStamp) ||
mBaseDataPoint->mTimeStamp < cutoff) {
return aLastObservedPosition;
}
// For the actual resampling, connect the last two data points with a line and
// sample along that line.
TimeStamp t1 = mBaseDataPoint->mTimeStamp;
TimeStamp t2 = mLatestDataPoint->mTimeStamp;
double t = (aTimeStamp - t1) / (t2 - t1);
double x1 = mBaseDataPoint->mPosition.x;
double x2 = mLatestDataPoint->mPosition.x;
double y1 = mBaseDataPoint->mPosition.y;
double y2 = mLatestDataPoint->mPosition.y;
int32_t resampledX = round(x1 + t * (x2 - x1));
int32_t resampledY = round(y1 + t * (y2 - y1));
return ScreenIntPoint(resampledX, resampledY);
}
void TouchResampler::CurrentTouches::UpdateFromEvent(
const MultiTouchInput& aInput) {
switch (aInput.mType) {
case MultiTouchInput::MULTITOUCH_START: {
// A new touch has been added; make sure mTouches reflects the current
// touches in the event.
nsTArray<TouchInfo> newTouches;
for (const auto& touch : aInput.mTouches) {
const auto touchInfo = TouchByIdentifier(touch.mIdentifier);
if (touchInfo != mTouches.end()) {
// This is one of the existing touches.
newTouches.AppendElement(std::move(*touchInfo));
mTouches.RemoveElementAt(touchInfo);
} else {
// This is the new touch.
newTouches.AppendElement(TouchInfo{
touch.mIdentifier, Nothing(),
Some(DataPoint{aInput.mTimeStamp, touch.mScreenPoint})});
}
}
MOZ_ASSERT(mTouches.IsEmpty(), "Missing touch end before touch start?");
mTouches = std::move(newTouches);
break;
}
case MultiTouchInput::MULTITOUCH_MOVE: {
// The touches have moved.
// Add position information to the history data points.
for (const auto& touch : aInput.mTouches) {
const auto touchInfo = TouchByIdentifier(touch.mIdentifier);
MOZ_ASSERT(touchInfo != mTouches.end());
if (touchInfo != mTouches.end()) {
touchInfo->Update(touch, aInput.mTimeStamp);
}
}
mLatestDataPointTime = aInput.mTimeStamp;
break;
}
case MultiTouchInput::MULTITOUCH_END: {
// A touch has been removed.
MOZ_RELEASE_ASSERT(aInput.mTouches.Length() == 1);
const auto touchInfo = TouchByIdentifier(aInput.mTouches[0].mIdentifier);
MOZ_ASSERT(touchInfo != mTouches.end());
if (touchInfo != mTouches.end()) {
mTouches.RemoveElementAt(touchInfo);
}
break;
}
case MultiTouchInput::MULTITOUCH_CANCEL:
// All touches are canceled.
mTouches.Clear();
break;
}
}
nsTArray<TouchResampler::TouchInfo>::iterator
TouchResampler::CurrentTouches::TouchByIdentifier(int32_t aIdentifier) {
return std::find_if(mTouches.begin(), mTouches.end(),
[aIdentifier](const TouchInfo& info) {
return info.mIdentifier == aIdentifier;
});
}
ScreenIntPoint TouchResampler::CurrentTouches::ResampleTouchPositionAtTime(
int32_t aIdentifier, const ScreenIntPoint& aLastObservedPosition,
const TimeStamp& aTimeStamp) {
const auto touchInfo = TouchByIdentifier(aIdentifier);
MOZ_ASSERT(touchInfo != mTouches.end());
if (touchInfo != mTouches.end()) {
return touchInfo->ResampleAtTime(aLastObservedPosition, aTimeStamp);
}
return aLastObservedPosition;
}
} // namespace widget
} // namespace mozilla

Просмотреть файл

@ -1,191 +0,0 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_widget_TouchResampler_h
#define mozilla_widget_TouchResampler_h
#include <queue>
#include <unordered_map>
#include "mozilla/Maybe.h"
#include "mozilla/TimeStamp.h"
#include "InputData.h"
namespace mozilla {
namespace widget {
/**
* De-jitters touch motions by resampling (interpolating or extrapolating) touch
* positions for the vsync timestamp.
*
* Touch resampling improves the touch panning experience on devices where touch
* positions are sampled at a rate that's not an integer multiple of the display
* refresh rate, for example 100Hz touch sampling on a 60Hz display: Without
* resampling, we would alternate between taking one touch sample or two touch
* samples into account each frame, creating a jittery motion ("small step, big
* step, small step, big step").
* Intended for use on Android, where both touch events and vsync notifications
* arrive on the same thread, the Java UI thread.
* This class is not thread safe.
*
* TouchResampler operates in the following way:
*
* Original events are fed into ProcessEvent().
* Outgoing events (potentially resampled for resampling) are added to a queue
* and can be consumed by calling ConsumeOutgoingEvents(). Touch events which
* are not touch move events are forwarded instantly and not resampled. Only
* touch move events are resampled. Whenever a touch move event is received, it
* gets delayed until NotifyFrame() is called, at which point it is resampled
* into a resampled version for the given frame timestamp, and added to the
* outgoing queue. If no touch move event is received between two consecutive
* frames, this is treated as a stop in the touch motion. If the last outgoing
* event was an resampled touch move event, we return back to the non-resampled
* state by emitting a copy of the last original touch move event, which has
* unmodified position data. Touch events which are not touch move events also
* force a return to the non-resampled state before they are moved to the
* outgoing queue.
*/
class TouchResampler final {
public:
// Feed a touch event into the interpolater. Returns an ID that can be used to
// match outgoing events to this incoming event, to track data associated with
// this event.
uint64_t ProcessEvent(MultiTouchInput&& aInput);
// Emit events, potentially resampled, for this timestamp. The events are put
// into the outgoing queue. May not emit any events if there's no update.
void NotifyFrame(const TimeStamp& aTimeStamp);
// Returns true between the start and the end of a touch gesture. During this
// time, the caller should keep itself registered with the system frame
// callback mechanism, so that NotifyFrame() can be called on every frame.
// (Otherwise, if we only registered the callback after receiving a touch move
// event, the frame callback might be delayed by a full frame.)
bool InTouchingState() const { return mCurrentTouches.HasTouch(); }
struct OutgoingEvent {
// The event, potentially modified from the original for resampling.
MultiTouchInput mEvent;
// Some(eventId) if this event is a modified version of an original event,
// Nothing() if this is an extra event.
Maybe<uint64_t> mEventId;
};
// Returns the outgoing events that were produced since the last call.
// No event IDs will be skipped. Returns at least one outgoing event for each
// incoming event (possibly after a delay), and potential extra events with
// no originating event ID.
// Outgoing events should be consumed after every call to ProcessEvent() and
// after every call to NotifyFrame().
std::queue<OutgoingEvent> ConsumeOutgoingEvents() {
return std::move(mOutgoingEvents);
}
private:
// Add the event to the outgoing queue.
void EmitEvent(MultiTouchInput&& aInput, uint64_t aEventId) {
mLastEmittedEventTime = aInput.mTimeStamp;
mOutgoingEvents.push(OutgoingEvent{std::move(aInput), Some(aEventId)});
}
// Emit an event that does not correspond to an incoming event.
void EmitExtraEvent(MultiTouchInput&& aInput) {
mLastEmittedEventTime = aInput.mTimeStamp;
mOutgoingEvents.push(OutgoingEvent{std::move(aInput), Nothing()});
}
// Move any touch move events that we deferred for resampling to the outgoing
// queue unmodified, leaving mDeferredTouchMoveEvents empty.
void FlushDeferredTouchMoveEventsUnresampled();
// Must only be called if mInResampledState is true and
// mDeferredTouchMoveEvents is empty. Emits mOriginalOfResampledTouchMove,
// with a potentially adjusted timestamp for correct ordering.
void ReturnToNonResampledState();
// Takes historical touch data from mRemainingTouchData and prepends it to the
// data in aInput.
void PrependLeftoverHistoricalData(MultiTouchInput* aInput);
struct DataPoint {
TimeStamp mTimeStamp;
ScreenIntPoint mPosition;
};
struct TouchInfo {
void Update(const SingleTouchData& aTouch, const TimeStamp& aEventTime);
ScreenIntPoint ResampleAtTime(const ScreenIntPoint& aLastObservedPosition,
const TimeStamp& aTimeStamp);
int32_t mIdentifier = 0;
Maybe<DataPoint> mBaseDataPoint;
Maybe<DataPoint> mLatestDataPoint;
};
struct CurrentTouches {
void UpdateFromEvent(const MultiTouchInput& aInput);
bool HasTouch() const { return !mTouches.IsEmpty(); }
TimeStamp LatestDataPointTime() { return mLatestDataPointTime; }
ScreenIntPoint ResampleTouchPositionAtTime(
int32_t aIdentifier, const ScreenIntPoint& aLastObservedPosition,
const TimeStamp& aTimeStamp);
void ClearDataPoints() {
for (auto& touch : mTouches) {
touch.mBaseDataPoint = Nothing();
touch.mLatestDataPoint = Nothing();
}
}
private:
nsTArray<TouchInfo>::iterator TouchByIdentifier(int32_t aIdentifier);
nsTArray<TouchInfo> mTouches;
TimeStamp mLatestDataPointTime;
};
// The current touch positions with historical data points. This data only
// contains original non-resampled positions from the incoming touch events.
CurrentTouches mCurrentTouches;
// Incoming touch move events are stored here until NotifyFrame is called.
std::queue<std::pair<MultiTouchInput, uint64_t>> mDeferredTouchMoveEvents;
// Stores any touch samples that were not included in the last emitted touch
// move event because they were in the future compared to the emitted event's
// timestamp. These data points should be prepended to the historical data of
// the next emitted touch move evnt.
// Can only be non-empty if mInResampledState is true.
std::unordered_map<int32_t, nsTArray<SingleTouchData::HistoricalTouchData>>
mRemainingTouchData;
// If we're in an resampled state, because the last outgoing event was a
// resampled touch move event, then this contains a copy of the unresampled,
// original touch move event.
// Some() iff mInResampledState is true.
Maybe<MultiTouchInput> mOriginalOfResampledTouchMove;
// The stream of outgoing events that can be consumed by our caller.
std::queue<OutgoingEvent> mOutgoingEvents;
// The timestamp of the event that was emitted most recently, or the null
// timestamp if no event has been emitted yet.
TimeStamp mLastEmittedEventTime;
uint64_t mNextEventId = 0;
// True if the last outgoing event was a touch move event with an resampled
// position. We only want to stay in this state as long as a continuous stream
// of touch move events is coming in.
bool mInResampledState = false;
};
} // namespace widget
} // namespace mozilla
#endif // mozilla_widget_TouchResampler_h

Просмотреть файл

@ -34,7 +34,6 @@
#include "mozilla/gfx/DataSurfaceHelpers.h"
#include "mozilla/gfx/Types.h"
#include "mozilla/layers/RenderTrace.h"
#include "mozilla/widget/AndroidVsync.h"
#include <algorithm>
using mozilla::Unused;
@ -101,7 +100,6 @@ using mozilla::gfx::SurfaceFormat;
#include "mozilla/java/PanZoomControllerNatives.h"
#include "mozilla/java/SessionAccessibilityWrappers.h"
#include "ScreenHelperAndroid.h"
#include "TouchResampler.h"
#include "GeckoProfiler.h" // For AUTO_PROFILER_LABEL
#include "nsPrintfCString.h"
@ -136,8 +134,6 @@ static bool sFailedToCreateGLContext = false;
static const double SWIPE_MAX_PINCH_DELTA_INCHES = 0.4;
static const double SWIPE_MIN_DISTANCE_INCHES = 0.6;
static const double kTouchResampleVsyncAdjustMs = 5.0;
static const int32_t INPUT_RESULT_UNHANDLED =
java::PanZoomController::INPUT_RESULT_UNHANDLED;
static const int32_t INPUT_RESULT_HANDLED =
@ -182,23 +178,10 @@ using WindowPtr = jni::NativeWeakPtr<GeckoViewSupport>;
* it separate from GeckoViewSupport.
*/
class NPZCSupport final
: public java::PanZoomController::NativeProvider::Natives<NPZCSupport>,
public AndroidVsync::Observer {
: public java::PanZoomController::NativeProvider::Natives<NPZCSupport> {
WindowPtr mWindow;
java::PanZoomController::NativeProvider::WeakRef mNPZC;
// Stores the returnResult of each pending motion event between
// HandleMotionEvent and FinishHandlingMotionEvent.
std::queue<std::pair<uint64_t, java::GeckoResult::GlobalRef>>
mPendingMotionEventReturnResults;
RefPtr<AndroidVsync> mAndroidVsync;
TouchResampler mTouchResampler;
int mPreviousButtons = 0;
bool mListeningToVsync = false;
// Only true if mAndroidVsync is non-null and the resampling pref is set.
bool mTouchResamplingEnabled = false;
int mPreviousButtons;
template <typename Lambda>
class InputEvent final : public nsAppShell::Event {
@ -261,27 +244,14 @@ class NPZCSupport final
NPZCSupport(WindowPtr aWindow,
const java::PanZoomController::NativeProvider::LocalRef& aNPZC)
: mWindow(aWindow), mNPZC(aNPZC) {
: mWindow(aWindow), mNPZC(aNPZC), mPreviousButtons(0) {
#if defined(DEBUG)
auto win(mWindow.Access());
MOZ_ASSERT(!!win);
#endif // defined(DEBUG)
// Use vsync for touch resampling on API level 19 and above.
// See gfxAndroidPlatform::CreateHardwareVsyncSource() for comparison.
if (AndroidBridge::Bridge() &&
AndroidBridge::Bridge()->GetAPIVersion() >= 19) {
mAndroidVsync = AndroidVsync::GetInstance();
}
}
~NPZCSupport() {
if (mListeningToVsync) {
MOZ_RELEASE_ASSERT(mAndroidVsync);
mAndroidVsync->UnregisterObserver(this, AndroidVsync::INPUT);
mListeningToVsync = false;
}
}
~NPZCSupport() {}
using Base::AttachNative;
using Base::DisposeNative;
@ -600,6 +570,23 @@ class NPZCSupport final
auto returnResult = java::GeckoResult::Ref::From(aResult);
auto eventData =
java::PanZoomController::MotionEventData::Ref::From(aEventData);
RefPtr<IAPZCTreeManager> controller;
if (auto window = mWindow.Access()) {
nsWindow* gkWindow = window->GetNsWindow();
if (gkWindow) {
controller = gkWindow->mAPZC;
}
}
if (!controller) {
if (returnResult) {
returnResult->Complete(
java::sdk::Integer::ValueOf(INPUT_RESULT_UNHANDLED));
}
return;
}
nsTArray<int32_t> pointerId(eventData->PointerId()->GetElements());
size_t pointerCount = pointerId.Length();
MultiTouchInput::MultiTouchType type;
@ -716,88 +703,10 @@ class NPZCSupport final
input.mTouches.AppendElement(singleTouchData);
}
if (mAndroidVsync &&
eventData->Action() == java::sdk::MotionEvent::ACTION_DOWN) {
// Query pref value at the beginning of a touch gesture so that we don't
// leave events stuck in the resampler after a pref flip.
mTouchResamplingEnabled = StaticPrefs::android_touch_resampling_enabled();
}
if (!mTouchResamplingEnabled) {
FinishHandlingMotionEvent(std::move(input),
java::GeckoResult::LocalRef(returnResult));
return;
}
uint64_t eventId = mTouchResampler.ProcessEvent(std::move(input));
mPendingMotionEventReturnResults.push(
{eventId, java::GeckoResult::GlobalRef(returnResult)});
RegisterOrUnregisterForVsync(mTouchResampler.InTouchingState());
ConsumeMotionEventsFromResampler();
}
void RegisterOrUnregisterForVsync(bool aNeedVsync) {
MOZ_RELEASE_ASSERT(mAndroidVsync);
if (aNeedVsync && !mListeningToVsync) {
mAndroidVsync->RegisterObserver(this, AndroidVsync::INPUT);
} else if (!aNeedVsync && mListeningToVsync) {
mAndroidVsync->UnregisterObserver(this, AndroidVsync::INPUT);
}
mListeningToVsync = aNeedVsync;
}
void OnVsync(const TimeStamp& aTimeStamp) override {
mTouchResampler.NotifyFrame(aTimeStamp - TimeDuration::FromMilliseconds(
kTouchResampleVsyncAdjustMs));
ConsumeMotionEventsFromResampler();
}
void ConsumeMotionEventsFromResampler() {
auto outgoing = mTouchResampler.ConsumeOutgoingEvents();
while (!outgoing.empty()) {
auto outgoingEvent = std::move(outgoing.front());
outgoing.pop();
java::GeckoResult::GlobalRef returnResult;
if (outgoingEvent.mEventId) {
// Look up the GeckoResult for this event.
// The outgoing events from the resampler are in the same order as the
// original events, and no event IDs are skipped.
MOZ_RELEASE_ASSERT(!mPendingMotionEventReturnResults.empty());
auto pair = mPendingMotionEventReturnResults.front();
mPendingMotionEventReturnResults.pop();
MOZ_RELEASE_ASSERT(pair.first == *outgoingEvent.mEventId);
returnResult = pair.second;
}
FinishHandlingMotionEvent(std::move(outgoingEvent.mEvent),
java::GeckoResult::LocalRef(returnResult));
}
}
void FinishHandlingMotionEvent(MultiTouchInput&& aInput,
java::GeckoResult::LocalRef&& aReturnResult) {
RefPtr<IAPZCTreeManager> controller;
if (auto window = mWindow.Access()) {
nsWindow* gkWindow = window->GetNsWindow();
if (gkWindow) {
controller = gkWindow->mAPZC;
}
}
if (!controller) {
if (aReturnResult) {
aReturnResult->Complete(
java::sdk::Integer::ValueOf(INPUT_RESULT_UNHANDLED));
}
return;
}
APZEventResult result =
controller->InputBridge()->ReceiveInputEvent(aInput);
APZEventResult result = controller->InputBridge()->ReceiveInputEvent(input);
if (result.mStatus == nsEventStatus_eConsumeNoDefault) {
if (aReturnResult) {
aReturnResult->Complete(java::sdk::Integer::ValueOf(
if (returnResult) {
returnResult->Complete(java::sdk::Integer::ValueOf(
(result.mHandledResult == Some(APZHandledResult::HandledByRoot))
? INPUT_RESULT_HANDLED
: INPUT_RESULT_HANDLED_CONTENT));
@ -806,13 +715,13 @@ class NPZCSupport final
}
// Dispatch APZ input event on Gecko thread.
PostInputEvent([aInput, result](nsWindow* window) {
WidgetTouchEvent touchEvent = aInput.ToWidgetTouchEvent(window);
PostInputEvent([input, result](nsWindow* window) {
WidgetTouchEvent touchEvent = input.ToWidgetTouchEvent(window);
window->ProcessUntransformedAPZEvent(&touchEvent, result);
window->DispatchHitTest(touchEvent);
});
if (!aReturnResult) {
if (!returnResult) {
// We don't care how APZ handled the event so we're done here.
return;
}
@ -822,16 +731,16 @@ class NPZCSupport final
// don't need to do any more work.
switch (result.mStatus) {
case nsEventStatus_eIgnore:
aReturnResult->Complete(
returnResult->Complete(
java::sdk::Integer::ValueOf(INPUT_RESULT_UNHANDLED));
break;
case nsEventStatus_eConsumeDoDefault:
aReturnResult->Complete(java::sdk::Integer::ValueOf(
returnResult->Complete(java::sdk::Integer::ValueOf(
ConvertAPZHandledResult(result.mHandledResult.value())));
break;
default:
MOZ_ASSERT_UNREACHABLE("Unexpected nsEventStatus");
aReturnResult->Complete(
returnResult->Complete(
java::sdk::Integer::ValueOf(INPUT_RESULT_UNHANDLED));
break;
}
@ -841,9 +750,9 @@ class NPZCSupport final
// Wait to see if APZ handled the event or not...
controller->AddInputBlockCallback(
result.mInputBlockId,
[aReturnResult = java::GeckoResult::GlobalRef(aReturnResult)](
[returnResult = java::GeckoResult::GlobalRef(returnResult)](
uint64_t aInputBlockId, APZHandledResult aHandledResult) {
aReturnResult->Complete(java::sdk::Integer::ValueOf(
returnResult->Complete(java::sdk::Integer::ValueOf(
ConvertAPZHandledResult(aHandledResult)));
});
}

Просмотреть файл

@ -219,7 +219,6 @@ UNIFIED_SOURCES += [
"ScrollbarDrawingMac.cpp",
"SharedWidgetUtils.cpp",
"TextEventDispatcher.cpp",
"TouchResampler.cpp",
"VsyncDispatcher.cpp",
"WidgetEventImpl.cpp",
"WidgetUtils.cpp",

Просмотреть файл

@ -1,941 +0,0 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include <initializer_list>
#include "InputData.h"
#include "Units.h"
#include "gtest/gtest.h"
#include "mozilla/Maybe.h"
#include "mozilla/TimeStamp.h"
#include "TouchResampler.h"
using namespace mozilla;
using widget::TouchResampler;
class TouchResamplerTest : public ::testing::Test {
protected:
virtual void SetUp() { baseTimeStamp = TimeStamp::Now(); }
TimeStamp Time(double aMilliseconds) {
return baseTimeStamp + TimeDuration::FromMilliseconds(aMilliseconds);
}
uint64_t ProcessEvent(
MultiTouchInput::MultiTouchType aType,
std::initializer_list<std::pair<TimeStamp, ScreenIntPoint>>
aHistoricalData,
const TimeStamp& aTimeStamp, const ScreenIntPoint& aPosition) {
MultiTouchInput input(aType, 0, aTimeStamp, 0);
input.mTouches.AppendElement(SingleTouchData(1, aPosition, {}, 0.0f, 0.0f));
for (const auto& histData : aHistoricalData) {
input.mTouches[0].mHistoricalData.AppendElement(
SingleTouchData::HistoricalTouchData{
histData.first, histData.second, {}, {}, 0.0f, 0.0f});
}
return resampler.ProcessEvent(std::move(input));
}
void CheckTime(const TimeStamp& aTimeStamp,
const TimeStamp& aExpectedTimeStamp) {
EXPECT_EQ((aTimeStamp - baseTimeStamp).ToMilliseconds(),
(aExpectedTimeStamp - baseTimeStamp).ToMilliseconds());
}
void CheckEvent(const MultiTouchInput& aEvent,
MultiTouchInput::MultiTouchType aExpectedType,
std::initializer_list<std::pair<TimeStamp, ScreenIntPoint>>
aExpectedHistoricalData,
const TimeStamp& aExpectedTimeStamp,
const ScreenIntPoint& aExpectedPosition) {
EXPECT_EQ(aEvent.mType, aExpectedType);
EXPECT_EQ(aEvent.mTouches.Length(), size_t(1));
EXPECT_EQ(aEvent.mTouches[0].mHistoricalData.Length(),
aExpectedHistoricalData.size());
for (size_t i = 0; i < aExpectedHistoricalData.size(); i++) {
CheckTime(aEvent.mTouches[0].mHistoricalData[i].mTimeStamp,
aExpectedHistoricalData.begin()[i].first);
EXPECT_EQ(aEvent.mTouches[0].mHistoricalData[i].mScreenPoint,
aExpectedHistoricalData.begin()[i].second);
}
CheckTime(aEvent.mTimeStamp, aExpectedTimeStamp);
EXPECT_EQ(aEvent.mTouches[0].mScreenPoint, aExpectedPosition);
}
struct ExpectedOutgoingEvent {
Maybe<uint64_t> mEventId;
MultiTouchInput::MultiTouchType mType = MultiTouchInput::MULTITOUCH_START;
std::initializer_list<std::pair<TimeStamp, ScreenIntPoint>> mHistoricalData;
TimeStamp mTimeStamp;
ScreenIntPoint mPosition;
};
void CheckOutgoingEvents(
std::initializer_list<ExpectedOutgoingEvent> aExpectedEvents) {
auto outgoing = resampler.ConsumeOutgoingEvents();
EXPECT_EQ(outgoing.size(), aExpectedEvents.size());
for (const auto& expectedEvent : aExpectedEvents) {
auto outgoingEvent = std::move(outgoing.front());
outgoing.pop();
EXPECT_EQ(outgoingEvent.mEventId, expectedEvent.mEventId);
CheckEvent(outgoingEvent.mEvent, expectedEvent.mType,
expectedEvent.mHistoricalData, expectedEvent.mTimeStamp,
expectedEvent.mPosition);
}
}
TimeStamp baseTimeStamp;
TouchResampler resampler;
};
TEST_F(TouchResamplerTest, BasicExtrapolation) {
// Execute the following sequence:
//
// 0----------10-------16-----20---------------32------------
// * touchstart at (10, 10)
// * touchmove at (20, 20)
// * frame
// * touchend at (20, 20)
// * frame
//
// And expect the following output:
//
// 0----------10-------16-----20---------------32------------
// * touchstart at (10, 10)
// * touchmove at (26, 26)
// * touchmove at (20, 20)
// * touchend at (20, 20)
//
// The first frame should emit an extrapolated touchmove from the position
// data in the touchstart and touchmove events.
// The touchend should force a synthesized touchmove that returns back to a
// non-resampled position.
EXPECT_FALSE(resampler.InTouchingState());
auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0),
ScreenIntPoint(10, 10));
EXPECT_TRUE(resampler.InTouchingState());
auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {}, Time(10.0),
ScreenIntPoint(20, 20));
resampler.NotifyFrame(Time(16.0));
CheckOutgoingEvents({
{Some(idStart0),
MultiTouchInput::MULTITOUCH_START,
{},
Time(0.0),
ScreenIntPoint(10, 10)},
{Some(idMove1),
MultiTouchInput::MULTITOUCH_MOVE,
{{Time(10.0), ScreenIntPoint(20, 20)}},
Time(16.0),
ScreenIntPoint(26, 26)},
});
auto idEnd2 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(20.0),
ScreenIntPoint(20, 20));
EXPECT_FALSE(resampler.InTouchingState());
CheckOutgoingEvents({
{Nothing(),
MultiTouchInput::MULTITOUCH_MOVE,
{},
Time(16.0),
ScreenIntPoint(20, 20)},
{Some(idEnd2),
MultiTouchInput::MULTITOUCH_END,
{},
Time(20.0),
ScreenIntPoint(20, 20)},
});
// No more events should be produced from here on out.
resampler.NotifyFrame(Time(32.0));
auto outgoing = resampler.ConsumeOutgoingEvents();
EXPECT_TRUE(outgoing.empty());
}
TEST_F(TouchResamplerTest, BasicInterpolation) {
// Same test as BasicExtrapolation, but with a frame time that's 10ms earlier.
//
// Execute the following sequence:
//
// 0------6---10-----------20--22------------30-------------
// * touchstart at (10, 10)
// * touchmove at (20, 20)
// * frame
// * touchend at (20, 20)
// * frame
//
// And expect the following output:
//
// 0------6---10-----------20--22------------30-------------
// * touchstart at (10, 10)
// * touchmove (16, 16)
// * touchmove (20, 20)
// * touchend at (20, 20)
//
// The first frame should emit an interpolated touchmove from the position
// data in the touchstart and touchmove events.
// The touchend should create a touchmove that returns back to a non-resampled
// position.
auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0),
ScreenIntPoint(10, 10));
EXPECT_TRUE(resampler.InTouchingState());
auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {}, Time(10.0),
ScreenIntPoint(20, 20));
resampler.NotifyFrame(Time(6.0));
CheckOutgoingEvents({
{Some(idStart0),
MultiTouchInput::MULTITOUCH_START,
{},
Time(0.0),
ScreenIntPoint(10, 10)},
{Some(idMove1),
MultiTouchInput::MULTITOUCH_MOVE,
{},
Time(6.0),
ScreenIntPoint(16, 16)},
});
auto idEnd2 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(20.0),
ScreenIntPoint(20, 20));
EXPECT_FALSE(resampler.InTouchingState());
CheckOutgoingEvents({
{Nothing(),
MultiTouchInput::MULTITOUCH_MOVE,
{},
Time(10.0),
ScreenIntPoint(20, 20)},
{Some(idEnd2),
MultiTouchInput::MULTITOUCH_END,
{},
Time(20.0),
ScreenIntPoint(20, 20)},
});
// No more events should be produced from here on out.
resampler.NotifyFrame(Time(22.0));
auto outgoing = resampler.ConsumeOutgoingEvents();
EXPECT_TRUE(outgoing.empty());
}
TEST_F(TouchResamplerTest, InterpolationFromHistoricalData) {
// Interpolate from the historical data in a touch move event.
//
// Execute the following sequence:
//
// 0----------10-------16-----20-----------30--32------------
// * touchstart at (10, 10)
// * [hist] at (20, 25) for
// `---------------* touchmove at (30, 30)
// * frame
// * touchend at (30, 30)
// * frame
//
// And expect the following output:
//
// 0----------10-------16-----20-----------30--32------------
// * touchstart at (10, 10)
// * [hist] at (20, 25) for
// `--------* touchmove at (26, 28)
// * touchmove at (30, 30)
// * touchend at (30, 30)
//
// The first frame should emit an interpolated touchmove from the position
// data in the touchmove event, and integrate the historical data point into
// the resampled event.
// The touchend should force a synthesized touchmove that returns back to a
// non-resampled position.
// This also tests that interpolation works for both x and y, by giving the
// historical datapoint different values for x and y.
// (26, 28) is 60% of the way from (20, 25) to (30, 30).
auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0),
ScreenIntPoint(10, 10));
auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE,
{{Time(10.0), ScreenIntPoint(20, 25)}},
Time(20.0), ScreenIntPoint(30, 30));
resampler.NotifyFrame(Time(16.0));
auto idEnd2 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(30.0),
ScreenIntPoint(30, 30));
resampler.NotifyFrame(Time(32.0));
CheckOutgoingEvents({
{Some(idStart0),
MultiTouchInput::MULTITOUCH_START,
{},
Time(0.0),
ScreenIntPoint(10, 10)},
{Some(idMove1),
MultiTouchInput::MULTITOUCH_MOVE,
{{Time(10.0), ScreenIntPoint(20, 25)}},
Time(16.0),
ScreenIntPoint(26, 28)},
{Nothing(),
MultiTouchInput::MULTITOUCH_MOVE,
{},
Time(20.0),
ScreenIntPoint(30, 30)},
{Some(idEnd2),
MultiTouchInput::MULTITOUCH_END,
{},
Time(30.0),
ScreenIntPoint(30, 30)},
});
}
TEST_F(TouchResamplerTest, MultipleTouches) {
EXPECT_FALSE(resampler.InTouchingState());
// Touch start
MultiTouchInput inputStart0(MultiTouchInput::MULTITOUCH_START, 0, Time(0.0),
0);
inputStart0.mTouches.AppendElement(
SingleTouchData(1, ScreenIntPoint(10, 10), {}, 0.0f, 0.0f));
auto idStart0 = resampler.ProcessEvent(std::move(inputStart0));
EXPECT_TRUE(resampler.InTouchingState());
// Touch move
MultiTouchInput inputMove1(MultiTouchInput::MULTITOUCH_MOVE, 0, Time(20.0),
0);
inputMove1.mTouches.AppendElement(
SingleTouchData(1, ScreenIntPoint(30, 30), {}, 0.0f, 0.0f));
inputMove1.mTouches[0].mHistoricalData.AppendElement(
SingleTouchData::HistoricalTouchData{
Time(10.0), ScreenIntPoint(20, 25), {}, {}, 0.0f, 0.0f});
auto idMove1 = resampler.ProcessEvent(std::move(inputMove1));
EXPECT_TRUE(resampler.InTouchingState());
// Frame
resampler.NotifyFrame(Time(16.0));
// Touch move
MultiTouchInput inputMove2(MultiTouchInput::MULTITOUCH_MOVE, 0, Time(30.0),
0);
inputMove2.mTouches.AppendElement(
SingleTouchData(1, ScreenIntPoint(30, 40), {}, 0.0f, 0.0f));
auto idMove2 = resampler.ProcessEvent(std::move(inputMove2));
EXPECT_TRUE(resampler.InTouchingState());
// Touch start
MultiTouchInput inputStart3(MultiTouchInput::MULTITOUCH_START, 0, Time(30.0),
0);
inputStart3.mTouches.AppendElement(
SingleTouchData(2, ScreenIntPoint(100, 10), {}, 0.0f, 0.0f));
inputStart3.mTouches.AppendElement(
SingleTouchData(1, ScreenIntPoint(30, 40), {}, 0.0f, 0.0f));
auto idStart3 = resampler.ProcessEvent(std::move(inputStart3));
EXPECT_TRUE(resampler.InTouchingState());
// Touch move
MultiTouchInput inputMove4(MultiTouchInput::MULTITOUCH_MOVE, 0, Time(40.0),
0);
inputMove4.mTouches.AppendElement(
SingleTouchData(1, ScreenIntPoint(30, 50), {}, 0.0f, 0.0f));
inputMove4.mTouches.AppendElement(
SingleTouchData(2, ScreenIntPoint(100, 30), {}, 0.0f, 0.0f));
auto idMove4 = resampler.ProcessEvent(std::move(inputMove4));
EXPECT_TRUE(resampler.InTouchingState());
// Frame
resampler.NotifyFrame(Time(32.0));
// Touch move
MultiTouchInput inputMove5(MultiTouchInput::MULTITOUCH_MOVE, 0, Time(50.0),
0);
inputMove5.mTouches.AppendElement(
SingleTouchData(1, ScreenIntPoint(30, 60), {}, 0.0f, 0.0f));
inputMove5.mTouches.AppendElement(
SingleTouchData(2, ScreenIntPoint(100, 40), {}, 0.0f, 0.0f));
auto idMove5 = resampler.ProcessEvent(std::move(inputMove5));
EXPECT_TRUE(resampler.InTouchingState());
// Touch end
MultiTouchInput inputEnd6(MultiTouchInput::MULTITOUCH_END, 0, Time(50.0), 0);
// Touch point with identifier 1 is lifted
inputEnd6.mTouches.AppendElement(
SingleTouchData(1, ScreenIntPoint(30, 60), {}, 0.0f, 0.0f));
auto idEnd6 = resampler.ProcessEvent(std::move(inputEnd6));
EXPECT_TRUE(resampler.InTouchingState());
// Frame
resampler.NotifyFrame(Time(48.0));
// Touch move
MultiTouchInput inputMove7(MultiTouchInput::MULTITOUCH_MOVE, 0, Time(60.0),
0);
inputMove7.mTouches.AppendElement(
SingleTouchData(2, ScreenIntPoint(100, 60), {}, 0.0f, 0.0f));
auto idMove7 = resampler.ProcessEvent(std::move(inputMove7));
EXPECT_TRUE(resampler.InTouchingState());
// Frame
resampler.NotifyFrame(Time(64.0));
// Touch end
MultiTouchInput inputEnd8(MultiTouchInput::MULTITOUCH_END, 0, Time(70.0), 0);
// Touch point with identifier 2 is lifted
inputEnd8.mTouches.AppendElement(
SingleTouchData(2, ScreenIntPoint(100, 60), {}, 0.0f, 0.0f));
auto idEnd8 = resampler.ProcessEvent(std::move(inputEnd8));
EXPECT_FALSE(resampler.InTouchingState());
// Check outgoing events
auto outgoing = resampler.ConsumeOutgoingEvents();
EXPECT_EQ(outgoing.size(), size_t(9));
auto outgoingStart0 = std::move(outgoing.front());
outgoing.pop();
EXPECT_EQ(outgoingStart0.mEventId, Some(idStart0));
CheckEvent(outgoingStart0.mEvent, MultiTouchInput::MULTITOUCH_START, {},
Time(0.0), ScreenIntPoint(10, 10));
auto outgoingMove1 = std::move(outgoing.front());
outgoing.pop();
EXPECT_EQ(outgoingMove1.mEventId, Some(idMove1));
// (26, 28) is 60% of the way from (20, 25) to (30, 30).
CheckEvent(outgoingMove1.mEvent, MultiTouchInput::MULTITOUCH_MOVE,
{{Time(10.0), ScreenIntPoint(20, 25)}}, Time(16.0),
ScreenIntPoint(26, 28));
auto outgoingMove2 = std::move(outgoing.front());
outgoing.pop();
EXPECT_EQ(outgoingMove2.mEventId, Some(idMove2));
CheckEvent(outgoingMove2.mEvent, MultiTouchInput::MULTITOUCH_MOVE,
{{Time(20.0), ScreenIntPoint(30, 30)}}, Time(30.0),
ScreenIntPoint(30, 40));
auto outgoingStart3 = std::move(outgoing.front());
outgoing.pop();
EXPECT_EQ(outgoingStart3.mEventId, Some(idStart3));
EXPECT_EQ(outgoingStart3.mEvent.mType, MultiTouchInput::MULTITOUCH_START);
CheckTime(outgoingStart3.mEvent.mTimeStamp, Time(30.0));
EXPECT_EQ(outgoingStart3.mEvent.mTouches.Length(), size_t(2));
// touch order should be taken from the original touch start event
EXPECT_EQ(outgoingStart3.mEvent.mTouches[0].mIdentifier, 2);
EXPECT_EQ(outgoingStart3.mEvent.mTouches[0].mScreenPoint,
ScreenIntPoint(100, 10));
EXPECT_EQ(outgoingStart3.mEvent.mTouches[1].mIdentifier, 1);
EXPECT_EQ(outgoingStart3.mEvent.mTouches[1].mScreenPoint,
ScreenIntPoint(30, 40));
auto outgoingMove4 = std::move(outgoing.front());
outgoing.pop();
EXPECT_EQ(outgoingMove4.mEventId, Some(idMove4));
EXPECT_EQ(outgoingMove4.mEvent.mType, MultiTouchInput::MULTITOUCH_MOVE);
CheckTime(outgoingMove4.mEvent.mTimeStamp, Time(32.0));
EXPECT_EQ(outgoingMove4.mEvent.mTouches.Length(), size_t(2));
// Touch order should be taken from the original touch move event.
// Both touches should be resampled.
EXPECT_EQ(outgoingMove4.mEvent.mTouches[0].mIdentifier, 1);
EXPECT_EQ(outgoingMove4.mEvent.mTouches[0].mScreenPoint,
ScreenIntPoint(30, 42));
EXPECT_EQ(outgoingMove4.mEvent.mTouches[1].mIdentifier, 2);
EXPECT_EQ(outgoingMove4.mEvent.mTouches[1].mScreenPoint,
ScreenIntPoint(100, 14));
auto outgoingMove5 = std::move(outgoing.front());
outgoing.pop();
EXPECT_EQ(outgoingMove5.mEventId, Some(idMove5));
EXPECT_EQ(outgoingMove5.mEvent.mType, MultiTouchInput::MULTITOUCH_MOVE);
CheckTime(outgoingMove5.mEvent.mTimeStamp, Time(50.0));
EXPECT_EQ(outgoingMove5.mEvent.mTouches.Length(), size_t(2));
// touch order should be taken from the original touch move event
EXPECT_EQ(outgoingMove5.mEvent.mTouches[0].mIdentifier, 1);
EXPECT_EQ(outgoingMove5.mEvent.mTouches[0].mScreenPoint,
ScreenIntPoint(30, 60));
EXPECT_EQ(outgoingMove5.mEvent.mTouches[0].mHistoricalData.Length(),
size_t(1));
CheckTime(outgoingMove5.mEvent.mTouches[0].mHistoricalData[0].mTimeStamp,
Time(40.0));
EXPECT_EQ(outgoingMove5.mEvent.mTouches[0].mHistoricalData[0].mScreenPoint,
ScreenIntPoint(30, 50));
EXPECT_EQ(outgoingMove5.mEvent.mTouches[1].mIdentifier, 2);
EXPECT_EQ(outgoingMove5.mEvent.mTouches[1].mScreenPoint,
ScreenIntPoint(100, 40));
EXPECT_EQ(outgoingMove5.mEvent.mTouches[1].mHistoricalData.Length(),
size_t(1));
CheckTime(outgoingMove5.mEvent.mTouches[1].mHistoricalData[0].mTimeStamp,
Time(40.0));
EXPECT_EQ(outgoingMove5.mEvent.mTouches[1].mHistoricalData[0].mScreenPoint,
ScreenIntPoint(100, 30));
auto outgoingEnd6 = std::move(outgoing.front());
outgoing.pop();
EXPECT_EQ(outgoingEnd6.mEventId, Some(idEnd6));
CheckEvent(outgoingEnd6.mEvent, MultiTouchInput::MULTITOUCH_END, {},
Time(50.0), ScreenIntPoint(30, 60));
auto outgoingMove7 = std::move(outgoing.front());
outgoing.pop();
EXPECT_EQ(outgoingMove7.mEventId, Some(idMove7));
// No extrapolation because the frame at 64.0 cleared the data points because
// there was no pending touch move event at that point
CheckEvent(outgoingMove7.mEvent, MultiTouchInput::MULTITOUCH_MOVE, {},
Time(60.0), ScreenIntPoint(100, 60));
EXPECT_EQ(outgoingMove7.mEvent.mTouches[0].mIdentifier, 2);
auto outgoingEnd8 = std::move(outgoing.front());
outgoing.pop();
EXPECT_EQ(outgoingEnd8.mEventId, Some(idEnd8));
CheckEvent(outgoingEnd8.mEvent, MultiTouchInput::MULTITOUCH_END, {},
Time(70.0), ScreenIntPoint(100, 60));
}
TEST_F(TouchResamplerTest, MovingPauses) {
auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0),
ScreenIntPoint(10, 10));
auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {}, Time(10.0),
ScreenIntPoint(20, 20));
resampler.NotifyFrame(Time(16.0));
auto idMove2 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {}, Time(30.0),
ScreenIntPoint(40, 40));
resampler.NotifyFrame(Time(32.0));
auto idMove3 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {}, Time(40.0),
ScreenIntPoint(50, 40));
resampler.NotifyFrame(Time(48.0));
resampler.NotifyFrame(Time(64.0));
auto idEnd4 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(70.0),
ScreenIntPoint(50, 40));
CheckOutgoingEvents({
{Some(idStart0),
MultiTouchInput::MULTITOUCH_START,
{},
Time(0.0),
ScreenIntPoint(10, 10)},
{Some(idMove1),
MultiTouchInput::MULTITOUCH_MOVE,
{{Time(10.0), ScreenIntPoint(20, 20)}},
Time(16.0),
ScreenIntPoint(26, 26)},
{Some(idMove2),
MultiTouchInput::MULTITOUCH_MOVE,
{{Time(30.0), ScreenIntPoint(40, 40)}},
Time(32.0),
ScreenIntPoint(42, 42)},
{Some(idMove3),
MultiTouchInput::MULTITOUCH_MOVE,
{{Time(40.0), ScreenIntPoint(50, 40)}},
Time(48.0),
ScreenIntPoint(58, 40)},
// There was no event between two frames here, so we expect a reset event,
// so that we pause at a non-resampled position.
{Nothing(),
MultiTouchInput::MULTITOUCH_MOVE,
{},
Time(48.0),
ScreenIntPoint(50, 40)},
{Some(idEnd4),
MultiTouchInput::MULTITOUCH_END,
{},
Time(70.0),
ScreenIntPoint(50, 40)},
});
}
TEST_F(TouchResamplerTest, MixedInterAndExtrapolation) {
auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0),
ScreenIntPoint(0, 0));
auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {}, Time(10.0),
ScreenIntPoint(0, 10));
resampler.NotifyFrame(Time(11.0)); // 16 - 5
auto idMove2 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE,
{{Time(20.0), ScreenIntPoint(0, 20)}}, Time(30.0),
ScreenIntPoint(0, 30));
resampler.NotifyFrame(Time(27.0)); // 32 - 5
auto idMove3 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {}, Time(40.0),
ScreenIntPoint(0, 40));
resampler.NotifyFrame(Time(43.0)); // 48 - 5
auto idMove4 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE,
{{Time(50.0), ScreenIntPoint(0, 50)}}, Time(60.0),
ScreenIntPoint(0, 60));
resampler.NotifyFrame(Time(59.0)); // 64 - 5
auto idEnd5 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(70.0),
ScreenIntPoint(0, 60));
CheckOutgoingEvents({
{Some(idStart0),
MultiTouchInput::MULTITOUCH_START,
{},
Time(0.0),
ScreenIntPoint(0, 0)},
{Some(idMove1),
MultiTouchInput::MULTITOUCH_MOVE,
{{Time(10.0), ScreenIntPoint(0, 10)}},
Time(11.0),
ScreenIntPoint(0, 11)},
{Some(idMove2),
MultiTouchInput::MULTITOUCH_MOVE,
{{Time(20.0), ScreenIntPoint(0, 20)}},
Time(27.0),
ScreenIntPoint(0, 27)},
{Some(idMove3),
MultiTouchInput::MULTITOUCH_MOVE,
{{Time(30.0), ScreenIntPoint(0, 30)},
{Time(40.0), ScreenIntPoint(0, 40)}},
Time(43.0),
ScreenIntPoint(0, 43)},
{Some(idMove4),
MultiTouchInput::MULTITOUCH_MOVE,
{{Time(50.0), ScreenIntPoint(0, 50)}},
Time(59.0),
ScreenIntPoint(0, 59)},
{Nothing(),
MultiTouchInput::MULTITOUCH_MOVE,
{},
Time(60.0),
ScreenIntPoint(0, 60)},
{Some(idEnd5),
MultiTouchInput::MULTITOUCH_END,
{},
Time(70.0),
ScreenIntPoint(0, 60)},
});
}
TEST_F(TouchResamplerTest, MultipleMoveEvents) {
// Test what happens if multiple touch move events appear between two frames.
// This scenario shouldn't occur on Android but we should be able to deal with
// it anyway. Check that we don't discard any event IDs.
auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0),
ScreenIntPoint(0, 0));
auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {}, Time(10.0),
ScreenIntPoint(0, 10));
resampler.NotifyFrame(Time(11.0)); // 16 - 5
auto idMove2 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE,
{{Time(20.0), ScreenIntPoint(0, 20)}}, Time(30.0),
ScreenIntPoint(0, 30));
auto idMove3 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {}, Time(40.0),
ScreenIntPoint(0, 40));
auto idMove4 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE,
{{Time(45.0), ScreenIntPoint(0, 45)}}, Time(50.0),
ScreenIntPoint(0, 50));
auto idMove5 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE, {}, Time(55.0),
ScreenIntPoint(0, 55));
resampler.NotifyFrame(Time(43.0)); // 48 - 5
resampler.NotifyFrame(Time(59.0)); // 64 - 5
auto idEnd5 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(70.0),
ScreenIntPoint(0, 60));
CheckOutgoingEvents({
{Some(idStart0),
MultiTouchInput::MULTITOUCH_START,
{},
Time(0.0),
ScreenIntPoint(0, 0)},
{Some(idMove1),
MultiTouchInput::MULTITOUCH_MOVE,
{{Time(10.0), ScreenIntPoint(0, 10)}},
Time(11.0),
ScreenIntPoint(0, 11)},
{Some(idMove2),
MultiTouchInput::MULTITOUCH_MOVE,
{{Time(20.0), ScreenIntPoint(0, 20)}},
Time(30.0),
ScreenIntPoint(0, 30)},
{Some(idMove3),
MultiTouchInput::MULTITOUCH_MOVE,
{},
Time(40.0),
ScreenIntPoint(0, 40)},
{Some(idMove4),
MultiTouchInput::MULTITOUCH_MOVE,
{},
Time(43.0),
ScreenIntPoint(0, 43)},
{Some(idMove5),
MultiTouchInput::MULTITOUCH_MOVE,
{{Time(45.0), ScreenIntPoint(0, 45)},
{Time(50.0), ScreenIntPoint(0, 50)},
{Time(55.0), ScreenIntPoint(0, 55)}},
Time(59.0),
ScreenIntPoint(0, 59)},
{Nothing(),
MultiTouchInput::MULTITOUCH_MOVE,
{},
Time(59.0),
ScreenIntPoint(0, 55)},
{Some(idEnd5),
MultiTouchInput::MULTITOUCH_END,
{},
Time(70.0),
ScreenIntPoint(0, 60)},
});
}
TEST_F(TouchResamplerTest, LimitFuturePrediction) {
auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0),
ScreenIntPoint(0, 0));
// Fingers move until time 44, then pause. UI thread is occupied until 64.
auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE,
{{Time(20.0), ScreenIntPoint(0, 20)},
{Time(32.0), ScreenIntPoint(0, 32)}},
Time(44.0), ScreenIntPoint(0, 44));
resampler.NotifyFrame(Time(59.0)); // 64 - 5
auto idEnd2 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(70.0),
ScreenIntPoint(0, 44));
CheckOutgoingEvents({
{Some(idStart0),
MultiTouchInput::MULTITOUCH_START,
{},
Time(0.0),
ScreenIntPoint(0, 0)},
// kTouchResampleMaxPredictMs == 8
// Refuse to predict more than 8ms into the future, the fingers might have
// paused. Make an event for time 52 (= 44 + 8) instead of 59.
{Some(idMove1),
MultiTouchInput::MULTITOUCH_MOVE,
{{Time(20.0), ScreenIntPoint(0, 20)},
{Time(32.0), ScreenIntPoint(0, 32)},
{Time(44.0), ScreenIntPoint(0, 44)}},
Time(52.0),
ScreenIntPoint(0, 52)},
{Nothing(),
MultiTouchInput::MULTITOUCH_MOVE,
{},
Time(52.0),
ScreenIntPoint(0, 44)},
{Some(idEnd2),
MultiTouchInput::MULTITOUCH_END,
{},
Time(70.0),
ScreenIntPoint(0, 44)},
});
}
TEST_F(TouchResamplerTest, LimitBacksampling) {
auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0),
ScreenIntPoint(0, 0));
// Fingers move until time 44, then pause. UI thread is occupied until 64.
// Then we get a frame callback with a very outdated frametime.
auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE,
{{Time(20.0), ScreenIntPoint(0, 20)},
{Time(32.0), ScreenIntPoint(0, 32)}},
Time(44.0), ScreenIntPoint(0, 44));
resampler.NotifyFrame(Time(11.0)); // 16 - 5
auto idEnd2 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(70.0),
ScreenIntPoint(0, 44));
CheckOutgoingEvents({
{Some(idStart0),
MultiTouchInput::MULTITOUCH_START,
{},
Time(0.0),
ScreenIntPoint(0, 0)},
// kTouchResampleMaxBacksampleMs == 20
// Refuse to sample further back than 20ms before the last data point.
// Make an event for time 24 (= 44 - 20) instead of time 11.
{Some(idMove1),
MultiTouchInput::MULTITOUCH_MOVE,
{{Time(20.0), ScreenIntPoint(0, 20)}},
Time(24.0),
ScreenIntPoint(0, 24)},
{Nothing(),
MultiTouchInput::MULTITOUCH_MOVE,
{{Time(32.0), ScreenIntPoint(0, 32)}},
Time(44.0),
ScreenIntPoint(0, 44)},
{Some(idEnd2),
MultiTouchInput::MULTITOUCH_END,
{},
Time(70.0),
ScreenIntPoint(0, 44)},
});
}
TEST_F(TouchResamplerTest, DontExtrapolateFromOldTouch) {
auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0),
ScreenIntPoint(0, 0));
// Fingers move until time 40, then pause. UI thread is occupied until 64.
auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE,
{{Time(20.0), ScreenIntPoint(0, 20)},
{Time(30.0), ScreenIntPoint(0, 30)}},
Time(40.0), ScreenIntPoint(0, 40));
resampler.NotifyFrame(Time(59.0)); // 64 - 5
auto idEnd2 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(70.0),
ScreenIntPoint(0, 44));
CheckOutgoingEvents({
{Some(idStart0),
MultiTouchInput::MULTITOUCH_START,
{},
Time(0.0),
ScreenIntPoint(0, 0)},
// kTouchResampleOldTouchThresholdMs == 17
// Refuse to extrapolate from a data point that's more than 17ms older
// than the frame time.
{Some(idMove1),
MultiTouchInput::MULTITOUCH_MOVE,
{{Time(20.0), ScreenIntPoint(0, 20)},
{Time(30.0), ScreenIntPoint(0, 30)}},
Time(40.0),
ScreenIntPoint(0, 40)},
{Some(idEnd2),
MultiTouchInput::MULTITOUCH_END,
{},
Time(70.0),
ScreenIntPoint(0, 44)},
});
}
TEST_F(TouchResamplerTest, DontExtrapolateIfTooOld) {
auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0),
ScreenIntPoint(0, 0));
// Fingers move until time 10, pause, and move again at 55.
// UI thread is occupied until 64.
auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE,
{{Time(10.0), ScreenIntPoint(0, 10)}}, Time(55.0),
ScreenIntPoint(0, 55));
resampler.NotifyFrame(Time(59.0)); // 64 - 5
auto idEnd2 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(70.0),
ScreenIntPoint(0, 60));
CheckOutgoingEvents({
{Some(idStart0),
MultiTouchInput::MULTITOUCH_START,
{},
Time(0.0),
ScreenIntPoint(0, 0)},
// kTouchResampleWindowSize == 40
// Refuse to resample between two data points that are more than 40ms
// apart.
{Some(idMove1),
MultiTouchInput::MULTITOUCH_MOVE,
{{Time(10.0), ScreenIntPoint(0, 10)}},
Time(55.0),
ScreenIntPoint(0, 55)},
{Some(idEnd2),
MultiTouchInput::MULTITOUCH_END,
{},
Time(70.0),
ScreenIntPoint(0, 60)},
});
}
TEST_F(TouchResamplerTest, DontInterpolateIfTooOld) {
auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0),
ScreenIntPoint(0, 0));
// Fingers move until time 10, pause, and move again at 60.
// UI thread is occupied until 64.
auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE,
{{Time(10.0), ScreenIntPoint(0, 10)}}, Time(60.0),
ScreenIntPoint(0, 60));
resampler.NotifyFrame(Time(59.0)); // 64 - 5
auto idEnd2 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(70.0),
ScreenIntPoint(0, 60));
CheckOutgoingEvents({
{Some(idStart0),
MultiTouchInput::MULTITOUCH_START,
{},
Time(0.0),
ScreenIntPoint(0, 0)},
// kTouchResampleWindowSize == 40
// Refuse to resample between two data points that are more than 40ms
// apart.
{Some(idMove1),
MultiTouchInput::MULTITOUCH_MOVE,
{{Time(10.0), ScreenIntPoint(0, 10)}},
Time(60.0),
ScreenIntPoint(0, 60)},
{Some(idEnd2),
MultiTouchInput::MULTITOUCH_END,
{},
Time(70.0),
ScreenIntPoint(0, 60)},
});
}
TEST_F(TouchResamplerTest, DiscardOutdatedHistoricalData) {
auto idStart0 = ProcessEvent(MultiTouchInput::MULTITOUCH_START, {}, Time(0.0),
ScreenIntPoint(0, 0));
auto idMove1 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE,
{{Time(10.0), ScreenIntPoint(0, 10)}}, Time(16.0),
ScreenIntPoint(0, 16));
resampler.NotifyFrame(Time(20.0));
auto idMove2 = ProcessEvent(MultiTouchInput::MULTITOUCH_MOVE,
{{Time(18.0), ScreenIntPoint(0, 18)}}, Time(25.0),
ScreenIntPoint(0, 25));
auto idEnd3 = ProcessEvent(MultiTouchInput::MULTITOUCH_END, {}, Time(35.0),
ScreenIntPoint(0, 25));
CheckOutgoingEvents({
{Some(idStart0),
MultiTouchInput::MULTITOUCH_START,
{},
Time(0.0),
ScreenIntPoint(0, 0)},
{Some(idMove1),
MultiTouchInput::MULTITOUCH_MOVE,
{{Time(10.0), ScreenIntPoint(0, 10)},
{Time(16.0), ScreenIntPoint(0, 16)}},
Time(20.0),
ScreenIntPoint(0, 20)},
// Discard the historical data point from time 18, because we've already
// sent out an event with time 20 and don't want to go back before that.
{Some(idMove2),
MultiTouchInput::MULTITOUCH_MOVE,
{},
Time(25.0),
ScreenIntPoint(0, 25)},
{Some(idEnd3),
MultiTouchInput::MULTITOUCH_END,
{},
Time(35.0),
ScreenIntPoint(0, 25)},
});
}

Просмотреть файл

@ -6,7 +6,6 @@
UNIFIED_SOURCES = [
"TestTimeConverter.cpp",
"TestTouchResampler.cpp",
]
FINAL_LIBRARY = "xul-gtest"