Bug 795567 - Part 3: Add panning restriction and retrieving of touch-action value to apzc. r=kats

This commit is contained in:
Nick Lebedev 2014-01-15 10:03:15 -05:00
parent 3bc8322d83
commit 7ee05aa952
4 changed files with 256 additions and 28 deletions

View File

@ -18,7 +18,6 @@
#include "mozilla/TouchEvents.h"
#include "nsDebug.h" // for NS_WARNING
#include "nsPoint.h" // for nsIntPoint
#include "nsTArray.h" // for nsTArray, nsTArray_Impl, etc
#include "nsThreadUtils.h" // for NS_IsMainThread
#include <algorithm> // for std::stable_sort
@ -43,6 +42,38 @@ APZCTreeManager::~APZCTreeManager()
{
}
void
APZCTreeManager::GetAllowedTouchBehavior(WidgetInputEvent* aEvent,
nsTArray<TouchBehaviorFlags>& aOutValues)
{
WidgetTouchEvent *touchEvent = aEvent->AsTouchEvent();
aOutValues.Clear();
for (size_t i = 0; i < touchEvent->touches.Length(); i++) {
// If aEvent wasn't transformed previously we might need to
// add transforming of the spt here.
mozilla::ScreenIntPoint spt;
spt.x = touchEvent->touches[i]->mRefPoint.x;
spt.y = touchEvent->touches[i]->mRefPoint.y;
nsRefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(spt);
aOutValues.AppendElement(apzc
? apzc->GetAllowedTouchBehavior(spt)
: AllowedTouchBehavior::UNKNOWN);
}
}
void
APZCTreeManager::SetAllowedTouchBehavior(const ScrollableLayerGuid& aGuid,
const nsTArray<TouchBehaviorFlags> &aValues)
{
nsRefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(aGuid);
if (apzc) {
apzc->SetAllowedTouchBehavior(aValues);
}
}
void
APZCTreeManager::AssertOnCompositorThread()
{

View File

@ -19,6 +19,7 @@
#include "nsISupportsImpl.h"
#include "nsTraceRefcnt.h" // for MOZ_COUNT_CTOR, etc
#include "mozilla/Vector.h" // for mozilla::Vector
#include "nsTArray.h" // for nsTArray, nsTArray_Impl, etc
class gfx3DMatrix;
template <class E> class nsTArray;
@ -28,6 +29,14 @@ class InputData;
namespace layers {
enum AllowedTouchBehavior {
NONE = 0,
VERTICAL_PAN = 1 << 0,
HORIZONTAL_PAN = 1 << 1,
ZOOM = 1 << 2,
UNKNOWN = 1 << 3
};
class Layer;
class AsyncPanZoomController;
class CompositorParent;
@ -57,6 +66,9 @@ class CompositorParent;
class APZCTreeManager {
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(APZCTreeManager)
typedef mozilla::layers::AllowedTouchBehavior AllowedTouchBehavior;
typedef uint32_t TouchBehaviorFlags;
public:
APZCTreeManager();
virtual ~APZCTreeManager();
@ -189,6 +201,22 @@ public:
*/
static float GetDPI() { return sDPI; }
/**
* Returns values of allowed touch-behavior for the touches of aEvent via out parameter.
* Internally performs asks appropriate AsyncPanZoomController to perform
* hit testing on its own.
*/
void GetAllowedTouchBehavior(WidgetInputEvent* aEvent,
nsTArray<TouchBehaviorFlags>& aOutValues);
/**
* Sets allowed touch behavior values for current touch-session for specific apzc (determined by guid).
* Should be invoked by the widget. Each value of the aValues arrays corresponds to the different
* touch point that is currently active.
*/
void SetAllowedTouchBehavior(const ScrollableLayerGuid& aGuid,
const nsTArray<TouchBehaviorFlags>& aValues);
/**
* This is a callback for AsyncPanZoomController to call when it wants to
* scroll in response to a touch-move event, or when it needs to hand off

View File

@ -127,6 +127,13 @@ using namespace mozilla::css;
namespace mozilla {
namespace layers {
typedef mozilla::layers::AllowedTouchBehavior AllowedTouchBehavior;
/**
* Specifies whether touch-action property is in force.
*/
static bool gTouchActionPropertyEnabled = false;
/**
* Constant describing the tolerance in distance we use, multiplied by the
* device DPI, before we start panning the screen. This is to prevent us from
@ -135,6 +142,13 @@ namespace layers {
*/
static float gTouchStartTolerance = 1.0f/2.0f;
/**
* Default touch behavior (is used when not touch behavior is set).
*/
static const uint32_t DefaultTouchBehavior = AllowedTouchBehavior::VERTICAL_PAN |
AllowedTouchBehavior::HORIZONTAL_PAN |
AllowedTouchBehavior::ZOOM;
/**
* Angle from axis within which we stay axis-locked
*/
@ -150,6 +164,15 @@ static const float AXIS_BREAKOUT_THRESHOLD = 1.0f/32.0f;
*/
static const double AXIS_BREAKOUT_ANGLE = M_PI / 8.0; // 22.5 degrees
/**
* Angle from axis to the line drawn by pan move.
* If angle is less than this value we can assume that panning
* can be done in allowed direction (horizontal or vertical).
* Currently used only for touch-action css property stuff and was
* added to keep behavior consistent with IE.
*/
static const double ALLOWED_DIRECT_PAN_ANGLE = M_PI / 3.0; // 60 degrees
/**
* The preferred axis locking style. See AxisLockMode for possible values.
*/
@ -358,6 +381,7 @@ AsyncPanZoomController::InitializeGlobalState()
return;
sInitialized = true;
Preferences::AddBoolVarCache(&gTouchActionPropertyEnabled, "layout.css.touch_action.enabled", gTouchActionPropertyEnabled);
Preferences::AddIntVarCache(&gPanRepaintInterval, "apz.pan_repaint_interval", gPanRepaintInterval);
Preferences::AddIntVarCache(&gFlingRepaintInterval, "apz.fling_repaint_interval", gFlingRepaintInterval);
Preferences::AddFloatVarCache(&gMinSkateSpeed, "apz.min_skate_speed", gMinSkateSpeed);
@ -390,9 +414,11 @@ AsyncPanZoomController::AsyncPanZoomController(uint64_t aLayersId,
mGeckoContentController(aGeckoContentController),
mRefPtrMonitor("RefPtrMonitor"),
mMonitor("AsyncPanZoomController"),
mTouchActionPropertyEnabled(gTouchActionPropertyEnabled),
mTouchListenerTimeoutTask(nullptr),
mX(MOZ_THIS_IN_INITIALIZER_LIST()),
mY(MOZ_THIS_IN_INITIALIZER_LIST()),
mPanDirRestricted(false),
mZoomConstraints(false, MIN_ZOOM, MAX_ZOOM),
mLastSampleTime(GetFrameTime()),
mState(NOTHING),
@ -561,6 +587,7 @@ nsEventStatus AsyncPanZoomController::HandleInputEvent(const InputData& aEvent)
nsEventStatus AsyncPanZoomController::OnTouchStart(const MultiTouchInput& aEvent) {
APZC_LOG("%p got a touch-start in state %d\n", this, mState);
mPanDirRestricted = false;
ScreenIntPoint point = GetFirstTouchScreenPoint(aEvent);
switch (mState) {
@ -624,6 +651,17 @@ nsEventStatus AsyncPanZoomController::OnTouchMove(const MultiTouchInput& aEvent)
return nsEventStatus_eIgnore;
}
if (mTouchActionPropertyEnabled &&
(GetTouchBehavior(0) & AllowedTouchBehavior::VERTICAL_PAN) &&
(GetTouchBehavior(0) & AllowedTouchBehavior::HORIZONTAL_PAN)) {
// User tries to trigger a touch behavior. If allowed touch behavior is vertical pan
// + horizontal pan (touch-action value is equal to AUTO) we can return ConsumeNoDefault
// status immediately to trigger cancel event further. It should happen independent of
// the parent type (whether it is scrolling or not).
StartPanning(aEvent);
return nsEventStatus_eConsumeNoDefault;
}
return StartPanning(aEvent);
}
@ -648,7 +686,13 @@ nsEventStatus AsyncPanZoomController::OnTouchMove(const MultiTouchInput& aEvent)
nsEventStatus AsyncPanZoomController::OnTouchEnd(const MultiTouchInput& aEvent) {
APZC_LOG("%p got a touch-end in state %d\n", this, mState);
{
// In case no touch behavior triggered previously we can avoid sending
// scroll events or requesting content repaint. This condition is added
// to make tests consistent - in case touch-action is NONE (and therefore
// no pans/zooms can be performed) we expected neither scroll or repaint
// events.
if (mState != NOTHING) {
ReentrantMonitorAutoEnter lock(mMonitor);
SendAsyncScrollEvent();
}
@ -934,6 +978,74 @@ const gfx::Point AsyncPanZoomController::GetAccelerationVector() {
return gfx::Point(mX.GetAccelerationFactor(), mY.GetAccelerationFactor());
}
void AsyncPanZoomController::HandlePanningWithTouchAction(double aAngle, TouchBehaviorFlags aBehavior) {
// Handling of cross sliding will need to be added in this method after touch-action released
// enabled by default.
if ((aBehavior & AllowedTouchBehavior::VERTICAL_PAN) && (aBehavior & AllowedTouchBehavior::HORIZONTAL_PAN)) {
if (mX.Scrollable() && mY.Scrollable()) {
if (IsCloseToHorizontal(aAngle, AXIS_LOCK_ANGLE)) {
mY.SetScrollingDisabled(true);
SetState(PANNING_LOCKED_X);
} else if (IsCloseToVertical(aAngle, AXIS_LOCK_ANGLE)) {
mX.SetScrollingDisabled(true);
SetState(PANNING_LOCKED_Y);
} else {
SetState(PANNING);
}
} else if (mX.Scrollable() || mY.Scrollable()) {
SetState(PANNING);
} else {
SetState(NOTHING);
}
} else if (aBehavior & AllowedTouchBehavior::HORIZONTAL_PAN) {
// Using bigger angle for panning to keep behavior consistent
// with IE.
if (IsCloseToHorizontal(aAngle, ALLOWED_DIRECT_PAN_ANGLE)) {
mY.SetScrollingDisabled(true);
SetState(PANNING_LOCKED_X);
mPanDirRestricted = true;
} else {
// Don't treat these touches as pan/zoom movements since 'touch-action' value
// requires it.
SetState(NOTHING);
}
} else if (aBehavior & AllowedTouchBehavior::VERTICAL_PAN) {
if (IsCloseToVertical(aAngle, ALLOWED_DIRECT_PAN_ANGLE)) {
mX.SetScrollingDisabled(true);
SetState(PANNING_LOCKED_Y);
mPanDirRestricted = true;
} else {
SetState(NOTHING);
}
} else {
SetState(NOTHING);
}
}
void AsyncPanZoomController::HandlePanning(double aAngle) {
if (!gCrossSlideEnabled && (!mX.Scrollable() || !mY.Scrollable())) {
SetState(PANNING);
} else if (IsCloseToHorizontal(aAngle, AXIS_LOCK_ANGLE)) {
mY.SetScrollingDisabled(true);
if (mX.Scrollable()) {
SetState(PANNING_LOCKED_X);
} else {
SetState(CROSS_SLIDING_X);
mX.SetScrollingDisabled(true);
}
} else if (IsCloseToVertical(aAngle, AXIS_LOCK_ANGLE)) {
mX.SetScrollingDisabled(true);
if (mY.Scrollable()) {
SetState(PANNING_LOCKED_Y);
} else {
SetState(CROSS_SLIDING_Y);
mY.SetScrollingDisabled(true);
}
} else {
SetState(PANNING);
}
}
nsEventStatus AsyncPanZoomController::StartPanning(const MultiTouchInput& aEvent) {
ReentrantMonitorAutoEnter lock(mMonitor);
@ -947,37 +1059,21 @@ nsEventStatus AsyncPanZoomController::StartPanning(const MultiTouchInput& aEvent
mY.StartTouch(point.y);
mLastEventTime = aEvent.mTime;
if (GetAxisLockMode() == FREE) {
SetState(PANNING);
return nsEventStatus_eConsumeNoDefault;
}
double angle = atan2(dy, dx); // range [-pi, pi]
angle = fabs(angle); // range [0, pi]
if (!gCrossSlideEnabled && (!mX.Scrollable() || !mY.Scrollable())) {
SetState(PANNING);
} else if (IsCloseToHorizontal(angle, AXIS_LOCK_ANGLE)) {
mY.SetScrollingDisabled(true);
if (mX.Scrollable()) {
SetState(PANNING_LOCKED_X);
} else {
SetState(CROSS_SLIDING_X);
mX.SetScrollingDisabled(true);
}
} else if (IsCloseToVertical(angle, AXIS_LOCK_ANGLE)) {
mX.SetScrollingDisabled(true);
if (mY.Scrollable()) {
SetState(PANNING_LOCKED_Y);
} else {
SetState(CROSS_SLIDING_Y);
mY.SetScrollingDisabled(true);
}
if (mTouchActionPropertyEnabled) {
HandlePanningWithTouchAction(angle, GetTouchBehavior(0));
} else {
SetState(PANNING);
if (GetAxisLockMode() == FREE) {
SetState(PANNING);
return nsEventStatus_eConsumeNoDefault;
}
HandlePanning(angle);
}
// Don't consume an event that starts a cross-slide.
// Don't consume an event that didn't trigger a panning.
return IsPanningState(mState) ? nsEventStatus_eConsumeNoDefault
: nsEventStatus_eIgnore;
}
@ -1060,7 +1156,7 @@ void AsyncPanZoomController::TrackTouch(const MultiTouchInput& aEvent) {
}
// If we're axis-locked, check if the user is trying to break the lock
if (GetAxisLockMode() == STICKY) {
if (GetAxisLockMode() == STICKY && !mPanDirRestricted) {
ScreenIntPoint point = GetFirstTouchScreenPoint(aEvent);
float dx = mX.PanDistance(point.x);
float dy = mY.PanDistance(point.y);
@ -1607,6 +1703,27 @@ void AsyncPanZoomController::ContentReceivedTouch(bool aPreventDefault) {
}
}
AsyncPanZoomController::TouchBehaviorFlags
AsyncPanZoomController::GetTouchBehavior(uint32_t touchIndex) {
if (touchIndex < mAllowedTouchBehaviors.Length()) {
return mAllowedTouchBehaviors[touchIndex];
}
return DefaultTouchBehavior;
}
AsyncPanZoomController::TouchBehaviorFlags
AsyncPanZoomController::GetAllowedTouchBehavior(ScreenIntPoint& aPoint) {
// Here we need to perform a hit testing over the touch-action regions attached to the
// layer associated with current apzc.
// Currently they are in progress, for more info see bug 928833.
return AllowedTouchBehavior::UNKNOWN;
}
void AsyncPanZoomController::SetAllowedTouchBehavior(const nsTArray<TouchBehaviorFlags>& aBehaviors) {
mAllowedTouchBehaviors.Clear();
mAllowedTouchBehaviors.AppendElements(aBehaviors);
}
void AsyncPanZoomController::SetState(PanZoomState aNewState) {
PanZoomState oldState;

View File

@ -65,6 +65,7 @@ class AsyncPanZoomController {
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AsyncPanZoomController)
typedef mozilla::MonitorAutoLock MonitorAutoLock;
typedef uint32_t TouchBehaviorFlags;
public:
enum GestureBehavior {
@ -292,6 +293,24 @@ public:
void AttemptScroll(const ScreenPoint& aStartPoint, const ScreenPoint& aEndPoint,
uint32_t aOverscrollHandoffChainIndex = 0);
/**
* Returns allowed touch behavior for the given point on the scrollable layer.
* Internally performs a kind of hit testing based on the regions constructed
* on the main thread and attached to the current scrollable layer. Each of such regions
* contains info about allowed touch behavior. If regions info isn't enough it returns
* UNKNOWN value and we should switch to the fallback approach - asking content.
* TODO: for now it's only a stub and returns hardcoded magic value. As soon as bug 928833
* is done we should integrate its logic here.
*/
TouchBehaviorFlags GetAllowedTouchBehavior(ScreenIntPoint& aPoint);
/**
* Sets allowed touch behavior for current touch session.
* This method is invoked by the APZCTreeManager which in its turn invoked by
* the widget after performing touch-action values retrieving.
*/
void SetAllowedTouchBehavior(const nsTArray<TouchBehaviorFlags>& aBehaviors);
/**
* A helper function for calling APZCTreeManager::DispatchScroll().
* Guards against the case where the APZC is being concurrently destroyed
@ -422,6 +441,16 @@ protected:
*/
ScreenIntPoint& GetFirstTouchScreenPoint(const MultiTouchInput& aEvent);
/**
* Sets the panning state basing on the pan direction angle and current touch-action value.
*/
void HandlePanningWithTouchAction(double angle, TouchBehaviorFlags value);
/**
* Sets the panning state ignoring the touch action value.
*/
void HandlePanning(double angle);
/**
* Sets up anything needed for panning. This takes us out of the "TOUCHING"
* state and starts actually panning us.
@ -508,6 +537,13 @@ private:
prevented the default actions yet. we still need to abort animations. */
};
/*
* Returns allowed touch behavior from the mAllowedTouchBehavior array.
* In case apzc didn't receive touch behavior values within the timeout
* it returns default value.
*/
TouchBehaviorFlags GetTouchBehavior(uint32_t touchIndex);
/**
* Helper to set the current state. Holds the monitor before actually setting
* it and fires content controller events based on state changes. Always set
@ -564,6 +600,12 @@ protected:
// function can be used, or the monitor can be held and then |mState| updated.
ReentrantMonitor mMonitor;
// Specifies whether we should use touch-action css property. Initialized from
// the preferences. This property (in comparison with the global one) simplifies
// testing apzc with (and without) touch-action property enabled concurrently
// (e.g. with the gtest framework).
bool mTouchActionPropertyEnabled;
private:
// Metrics of the container layer corresponding to this APZC. This is
// stored here so that it is accessible from the UI/controller thread.
@ -584,6 +626,10 @@ private:
AxisX mX;
AxisY mY;
// This flag is set to true when we are in a axis-locked pan as a result of
// the touch-action CSS property.
bool mPanDirRestricted;
// Most up-to-date constraints on zooming. These should always be reasonable
// values; for example, allowing a min zoom of 0.0 can cause very bad things
// to happen.
@ -622,6 +668,12 @@ private:
// and we don't want to queue the events back up again.
bool mHandlingTouchQueue;
// Values of allowed touch behavior for current touch points.
// Since there are maybe a few current active touch points per time (multitouch case)
// and each touch point should have its own value of allowed touch behavior- we're
// keeping an array of allowed touch behavior values, not the single value.
nsTArray<TouchBehaviorFlags> mAllowedTouchBehaviors;
RefPtr<AsyncPanZoomAnimation> mAnimation;
friend class Axis;