Bug 1120683 - Properly handle unapplying 3D projective transforms throughout APZ code. r=botond

Whenever the inverse of a 3D projective transform is applied to a point, only use the result if it has a positive w-coordinate.

When transforming by a matrix that we know should be 2D, assert to that effect.

Transformations of rectangles (as opposed to points) remain to be audited.
This commit is contained in:
Kevin Wern 2015-07-03 15:06:26 -04:00
parent db609af9f5
commit b4ac13dd45
6 changed files with 141 additions and 38 deletions

View File

@ -605,6 +605,7 @@ APZCTreeManager::ReceiveInputEvent(InputData& aEvent,
// gecko space should only consist of overscroll-cancelling transforms.
Matrix4x4 transformToGecko = GetScreenToApzcTransform(apzc)
* GetApzcToGeckoTransform(apzc);
MOZ_ASSERT(transformToGecko.Is2D());
ScreenPoint untransformedOrigin = TransformTo<ScreenPixel>(
transformToGecko, wheelInput.mOrigin);
@ -634,6 +635,7 @@ APZCTreeManager::ReceiveInputEvent(InputData& aEvent,
apzc->GetGuid(aOutTargetGuid);
Matrix4x4 transformToGecko = GetScreenToApzcTransform(apzc)
* GetApzcToGeckoTransform(apzc);
MOZ_ASSERT(transformToGecko.Is2D());
panInput.mPanStartPoint = TransformTo<ScreenPixel>(
transformToGecko, panInput.mPanStartPoint);
panInput.mPanDisplacement = TransformVector<ScreenPixel>(
@ -656,6 +658,7 @@ APZCTreeManager::ReceiveInputEvent(InputData& aEvent,
apzc->GetGuid(aOutTargetGuid);
Matrix4x4 outTransform = GetScreenToApzcTransform(apzc)
* GetApzcToGeckoTransform(apzc);
MOZ_ASSERT(outTransform.Is2D());
pinchInput.mFocusPoint = TransformTo<ScreenPixel>(
outTransform, pinchInput.mFocusPoint);
}
@ -676,6 +679,7 @@ APZCTreeManager::ReceiveInputEvent(InputData& aEvent,
apzc->GetGuid(aOutTargetGuid);
Matrix4x4 outTransform = GetScreenToApzcTransform(apzc)
* GetApzcToGeckoTransform(apzc);
MOZ_ASSERT(outTransform.Is2D());
tapInput.mPoint = TransformTo<ScreenPixel>(outTransform, tapInput.mPoint);
}
break;
@ -785,6 +789,8 @@ APZCTreeManager::ProcessTouchInput(MultiTouchInput& aInput,
Matrix4x4 transformToApzc = GetScreenToApzcTransform(mApzcForInputBlock);
Matrix4x4 transformToGecko = GetApzcToGeckoTransform(mApzcForInputBlock);
Matrix4x4 outTransform = transformToApzc * transformToGecko;
MOZ_ASSERT(outTransform.Is2D());
for (size_t i = 0; i < aInput.mTouches.Length(); i++) {
SingleTouchData& touchData = aInput.mTouches[i];
touchData.mScreenPoint = TransformTo<ScreenPixel>(
@ -825,6 +831,7 @@ APZCTreeManager::TransformCoordinateToGecko(const ScreenIntPoint& aPoint,
Matrix4x4 transformToApzc = GetScreenToApzcTransform(apzc);
Matrix4x4 transformToGecko = GetApzcToGeckoTransform(apzc);
Matrix4x4 outTransform = transformToApzc * transformToGecko;
MOZ_ASSERT(outTransform.Is2D());
*aOutTransformedPoint = TransformTo<LayoutDevicePixel>(outTransform, aPoint);
}
}
@ -892,6 +899,7 @@ APZCTreeManager::ProcessEvent(WidgetInputEvent& aEvent,
Matrix4x4 transformToApzc = GetScreenToApzcTransform(apzc);
Matrix4x4 transformToGecko = GetApzcToGeckoTransform(apzc);
Matrix4x4 outTransform = transformToApzc * transformToGecko;
MOZ_ASSERT(outTransform.Is2D());
aEvent.refPoint = TransformTo<LayoutDevicePixel>(outTransform, aEvent.refPoint);
}
return result;
@ -1188,8 +1196,9 @@ APZCTreeManager::GetRootNode() const
* @param aTarget the target APZC
* @param aStartPoint the start point of the displacement
* @param aEndPoint the end point of the displacement
* @return true on success, false if aStartPoint or aEndPoint cannot be transformed into target's coordinate space
*/
static void
static bool
TransformDisplacement(APZCTreeManager* aTreeManager,
AsyncPanZoomController* aSource,
AsyncPanZoomController* aTarget,
@ -1200,10 +1209,18 @@ TransformDisplacement(APZCTreeManager* aTreeManager,
ScreenPoint screenStart = TransformTo<ScreenPixel>(untransformToApzc, aStartPoint);
ScreenPoint screenEnd = TransformTo<ScreenPixel>(untransformToApzc, aEndPoint);
// Convert start and end points to aTarget's ParentLayer coordinates.
Matrix4x4 transformToApzc = aTreeManager->GetScreenToApzcTransform(aTarget);
aStartPoint = TransformTo<ParentLayerPixel>(transformToApzc, screenStart);
aEndPoint = TransformTo<ParentLayerPixel>(transformToApzc, screenEnd);
Maybe<ParentLayerPoint> startPoint = UntransformTo<ParentLayerPixel>(transformToApzc, screenStart);
Maybe<ParentLayerPoint> endPoint = UntransformTo<ParentLayerPixel>(transformToApzc, screenEnd);
if (!startPoint || !endPoint) {
return false;
}
aEndPoint = *endPoint;
aStartPoint = *startPoint;
return true;
}
bool
@ -1234,7 +1251,9 @@ APZCTreeManager::DispatchScroll(AsyncPanZoomController* aPrev,
// scroll grabbing to grab the scroll from it), don't bother doing the
// transformations in that case.
if (next != aPrev) {
TransformDisplacement(this, aPrev, next, aStartPoint, aEndPoint);
if (!TransformDisplacement(this, aPrev, next, aStartPoint, aEndPoint)) {
return false;
}
}
// Scroll |next|. If this causes overscroll, it will call DispatchScroll()
@ -1287,11 +1306,13 @@ APZCTreeManager::DispatchFling(AsyncPanZoomController* aPrev,
// Only transform when current apcz can be transformed with previous
if (startIndex > 0) {
TransformDisplacement(this,
if (!TransformDisplacement(this,
aOverscrollHandoffChain->GetApzcAtIndex(startIndex - 1),
current,
startPoint,
endPoint);
endPoint)) {
return false;
}
}
transformedVelocity = endPoint - startPoint;

View File

@ -951,7 +951,9 @@ nsEventStatus AsyncPanZoomController::HandleInputEvent(const InputData& aEvent,
switch (aEvent.mInputType) {
case MULTITOUCH_INPUT: {
MultiTouchInput multiTouchInput = aEvent.AsMultiTouchInput();
multiTouchInput.TransformToLocal(aTransformToApzc);
if (!multiTouchInput.TransformToLocal(aTransformToApzc)) {
return rv;
}
nsRefPtr<GestureEventListener> listener = GetGestureEventListener();
if (listener) {
@ -972,7 +974,9 @@ nsEventStatus AsyncPanZoomController::HandleInputEvent(const InputData& aEvent,
}
case PANGESTURE_INPUT: {
PanGestureInput panGestureInput = aEvent.AsPanGestureInput();
panGestureInput.TransformToLocal(aTransformToApzc);
if (!panGestureInput.TransformToLocal(aTransformToApzc)) {
return rv;
}
switch (panGestureInput.mType) {
case PanGestureInput::PANGESTURE_MAYSTART: rv = OnPanMayBegin(panGestureInput); break;
@ -989,21 +993,27 @@ nsEventStatus AsyncPanZoomController::HandleInputEvent(const InputData& aEvent,
}
case SCROLLWHEEL_INPUT: {
ScrollWheelInput scrollInput = aEvent.AsScrollWheelInput();
scrollInput.TransformToLocal(aTransformToApzc);
if (!scrollInput.TransformToLocal(aTransformToApzc)) {
return rv;
}
rv = OnScrollWheel(scrollInput);
break;
}
case PINCHGESTURE_INPUT: {
PinchGestureInput pinchInput = aEvent.AsPinchGestureInput();
pinchInput.TransformToLocal(aTransformToApzc);
if (!pinchInput.TransformToLocal(aTransformToApzc)) {
return rv;
}
rv = HandleGestureEvent(pinchInput);
break;
}
case TAPGESTURE_INPUT: {
TapGestureInput tapInput = aEvent.AsTapGestureInput();
tapInput.TransformToLocal(aTransformToApzc);
if (!tapInput.TransformToLocal(aTransformToApzc)) {
return rv;
}
rv = HandleGestureEvent(tapInput);
break;
@ -1396,12 +1406,14 @@ bool
AsyncPanZoomController::ConvertToGecko(const ScreenIntPoint& aPoint, CSSPoint* aOut)
{
if (APZCTreeManager* treeManagerLocal = GetApzcTreeManager()) {
Matrix4x4 transformToApzc = treeManagerLocal->GetScreenToApzcTransform(this);
Matrix4x4 transformToGecko = treeManagerLocal->GetApzcToGeckoTransform(this);
Matrix4x4 transformScreenToGecko = treeManagerLocal->GetScreenToApzcTransform(this)
* treeManagerLocal->GetApzcToGeckoTransform(this);
// NOTE: This isn't *quite* LayoutDevicePoint, we just don't have a name
// for this coordinate space and it maps the closest to LayoutDevicePoint.
MOZ_ASSERT(transformScreenToGecko.Is2D());
LayoutDevicePoint layoutPoint = TransformTo<LayoutDevicePixel>(
transformToApzc * transformToGecko, aPoint);
transformScreenToGecko, aPoint);
{ // scoped lock to access mFrameMetrics
ReentrantMonitorAutoEnter lock(mMonitor);
@ -1837,6 +1849,7 @@ ScreenPoint AsyncPanZoomController::ToScreenCoordinates(const ParentLayerPoint&
return TransformVector<ScreenPixel>(GetTransformToThis().Inverse(), aVector, aAnchor);
}
// TODO: figure out a good way to check the w-coordinate is positive and return the result
ParentLayerPoint AsyncPanZoomController::ToParentLayerCoordinates(const ScreenPoint& aVector,
const ScreenPoint& aAnchor) const {
return TransformVector<ParentLayerPixel>(GetTransformToThis(), aVector, aAnchor);
@ -1845,14 +1858,17 @@ ParentLayerPoint AsyncPanZoomController::ToParentLayerCoordinates(const ScreenPo
bool AsyncPanZoomController::Contains(const ScreenIntPoint& aPoint) const
{
Matrix4x4 transformToThis = GetTransformToThis();
ParentLayerIntPoint point = TransformTo<ParentLayerPixel>(transformToThis, aPoint);
Maybe<ParentLayerIntPoint> point = UntransformTo<ParentLayerPixel>(transformToThis, aPoint);
if (!point) {
return false;
}
ParentLayerIntRect cb;
{
ReentrantMonitorAutoEnter lock(mMonitor);
GetFrameMetrics().GetCompositionBounds().ToIntRect(&cb);
}
return cb.Contains(point);
return cb.Contains(*point);
}
ScreenCoord AsyncPanZoomController::PanDistance() const {

View File

@ -207,10 +207,7 @@ HitTestingTreeNode::Untransform(const ParentLayerPoint& aPoint) const
if (mApzc) {
localTransform = localTransform * mApzc->GetCurrentAsyncTransformWithOverscroll();
}
gfx::Point4D point = localTransform.Inverse().ProjectPoint(aPoint.ToUnknownPoint());
return point.HasPositiveWCoord()
? Some(ViewAs<LayerPixel>(point.As2DPoint()))
: Nothing();
return UntransformTo<LayerPixel>(localTransform.Inverse(), aPoint);
}
HitTestResult

View File

@ -145,6 +145,45 @@ static gfx::PointTyped<TargetUnits> TransformVector(const gfx::Matrix4x4& aTrans
return transformedEnd - transformedStart;
}
// UntransformTo() and UntransformVector() are like TransformTo() and
// TransformVector(), respectively, but are intended for cases where
// the transformation matrix is the inverse of a 3D projection. When
// using such transforms, the resulting Point4D is only meaningful
// if it has a positive w-coordinate. To handle this, these functions
// return a Maybe object which contains a value if and only if the
// result is meaningful
template <typename TargetUnits, typename SourceUnits>
static Maybe<gfx::PointTyped<TargetUnits>> UntransformTo(const gfx::Matrix4x4& aTransform,
const gfx::PointTyped<SourceUnits>& aPoint)
{
gfx::Point4D point = aTransform.ProjectPoint(aPoint.ToUnknownPoint());
if (!point.HasPositiveWCoord()) {
return Nothing();
}
return Some(ViewAs<TargetUnits>(point.As2DPoint()));
}
template <typename TargetUnits, typename SourceUnits>
static Maybe<gfx::IntPointTyped<TargetUnits>> UntransformTo(const gfx::Matrix4x4& aTransform,
const gfx::IntPointTyped<SourceUnits>& aPoint)
{
gfx::Point4D point = aTransform.ProjectPoint(aPoint.ToUnknownPoint());
if (!point.HasPositiveWCoord()) {
return Nothing();
}
return Some(RoundedToInt(ViewAs<TargetUnits>(point.As2DPoint())));
}
template <typename TargetUnits, typename SourceUnits>
static Maybe<gfx::PointTyped<TargetUnits>> UntransformVector(const gfx::Matrix4x4& aTransform,
const gfx::PointTyped<SourceUnits>& aVector,
const gfx::PointTyped<SourceUnits>& aAnchor) {
gfx::Point4D point = aTransform.ProjectPoint(aAnchor.ToUnknownPoint() + aVector.ToUnknownPoint())
- aTransform.ProjectPoint(aAnchor.ToUnknownPoint());
if (!point.HasPositiveWCoord()){
return Nothing();
}
return Some(ViewAs<TargetUnits>(point.As2DPoint()));
}
}
#endif

View File

@ -212,37 +212,67 @@ MultiTouchInput::MultiTouchInput(const WidgetMouseEvent& aMouseEvent)
1.0f));
}
void
bool
MultiTouchInput::TransformToLocal(const gfx::Matrix4x4& aTransform)
{
for (size_t i = 0; i < mTouches.Length(); i++) {
mTouches[i].mLocalScreenPoint = TransformTo<ParentLayerPixel>(aTransform, ScreenPoint(mTouches[i].mScreenPoint));
Maybe<ParentLayerIntPoint> point = UntransformTo<ParentLayerPixel>(aTransform, mTouches[i].mScreenPoint);
if (!point) {
return false;
}
mTouches[i].mLocalScreenPoint = *point;
}
return true;
}
void
bool
PanGestureInput::TransformToLocal(const gfx::Matrix4x4& aTransform)
{
mLocalPanStartPoint = TransformTo<ParentLayerPixel>(aTransform, mPanStartPoint);
mLocalPanDisplacement = TransformVector<ParentLayerPixel>(aTransform, mPanDisplacement, mPanStartPoint);
{
Maybe<ParentLayerPoint> panStartPoint = UntransformTo<ParentLayerPixel>(aTransform, mPanStartPoint);
if (!panStartPoint) {
return false;
}
mLocalPanStartPoint = *panStartPoint;
Maybe<ParentLayerPoint> panDisplacement = UntransformVector<ParentLayerPixel>(aTransform, mPanDisplacement, mPanStartPoint);
if (!panDisplacement) {
return false;
}
mLocalPanDisplacement = *panDisplacement;
return true;
}
void
bool
PinchGestureInput::TransformToLocal(const gfx::Matrix4x4& aTransform)
{
mLocalFocusPoint = TransformTo<ParentLayerPixel>(aTransform, mFocusPoint);
{
Maybe<ParentLayerPoint> point = UntransformTo<ParentLayerPixel>(aTransform, mFocusPoint);
if (!point) {
return false;
}
mLocalFocusPoint = *point;
return true;
}
void
bool
TapGestureInput::TransformToLocal(const gfx::Matrix4x4& aTransform)
{
mLocalPoint = TransformTo<ParentLayerPixel>(aTransform, mPoint);
Maybe<ParentLayerIntPoint> point = UntransformTo<ParentLayerPixel>(aTransform, mPoint);
if (!point) {
return false;
}
mLocalPoint = *point;
return true;
}
void
bool
ScrollWheelInput::TransformToLocal(const gfx::Matrix4x4& aTransform)
{
mLocalOrigin = TransformTo<ParentLayerPixel>(aTransform, mOrigin);
Maybe<ParentLayerPoint> point = UntransformTo<ParentLayerPixel>(aTransform, mOrigin);
if (!point) {
return false;
}
mLocalOrigin = *point;
return true;
}
} // namespace mozilla

View File

@ -239,7 +239,7 @@ public:
// and rotation angle.
explicit MultiTouchInput(const WidgetMouseEvent& aMouseEvent);
void TransformToLocal(const gfx::Matrix4x4& aTransform);
bool TransformToLocal(const gfx::Matrix4x4& aTransform);
MultiTouchType mType;
nsTArray<SingleTouchData> mTouches;
@ -311,7 +311,7 @@ public:
{
}
void TransformToLocal(const gfx::Matrix4x4& aTransform);
bool TransformToLocal(const gfx::Matrix4x4& aTransform);
PanGestureType mType;
ScreenPoint mPanStartPoint;
@ -374,7 +374,7 @@ public:
{
}
void TransformToLocal(const gfx::Matrix4x4& aTransform);
bool TransformToLocal(const gfx::Matrix4x4& aTransform);
PinchGestureType mType;
@ -444,7 +444,7 @@ public:
{
}
void TransformToLocal(const gfx::Matrix4x4& aTransform);
bool TransformToLocal(const gfx::Matrix4x4& aTransform);
TapGestureType mType;
@ -506,7 +506,7 @@ public:
mDeltaY(aDeltaY)
{}
void TransformToLocal(const gfx::Matrix4x4& aTransform);
bool TransformToLocal(const gfx::Matrix4x4& aTransform);
ScrollDeltaType mDeltaType;
ScrollMode mScrollMode;