Bug 864441 - Add APZC tests. r=kats
☠☠ backed out by 511fab5a9157 ☠ ☠
authorBenoit Girard <b56girard@gmail.com>
Thu, 20 Jun 2013 13:53:39 -0400
changeset 147476 4290ee7475c1238ea347593aa4a2ffbd5126d7b1
parent 147475 8508340fabb78cc93b0a11b9cafc90982be37f39
child 147477 e32ba820b8ce1862243989dc4c64ba7a5bd3f41c
push id2697
push userbbajaj@mozilla.com
push dateMon, 05 Aug 2013 18:49:53 +0000
treeherdermozilla-beta@dfec938c7b63 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskats
bugs864441
milestone24.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 864441 - Add APZC tests. r=kats
gfx/layers/composite/AsyncCompositionManager.h
gfx/layers/ipc/AsyncPanZoomController.cpp
gfx/layers/ipc/AsyncPanZoomController.h
gfx/src/nsRegion.cpp
gfx/tests/gtest/Makefile.in
gfx/tests/gtest/TestAsyncPanZoomController.cpp
--- a/gfx/layers/composite/AsyncCompositionManager.h
+++ b/gfx/layers/composite/AsyncCompositionManager.h
@@ -35,16 +35,24 @@ struct ViewTransform {
 
   operator gfx3DMatrix() const
   {
     return
       gfx3DMatrix::Translation(mTranslation.x, mTranslation.y, 0) *
       gfx3DMatrix::ScalingMatrix(mScale.scale, mScale.scale, 1);
   }
 
+  bool operator==(const ViewTransform& rhs) const {
+    return mTranslation == rhs.mTranslation && mScale == rhs.mScale;
+  }
+
+  bool operator!=(const ViewTransform& rhs) const {
+    return !(*this == rhs);
+  }
+
   LayerPoint mTranslation;
   CSSToScreenScale mScale;
 };
 
 /**
  * Manage async composition effects. This class is only used with OMTC and only
  * lives on the compositor thread. It is a layer on top of the layer manager
  * (LayerManagerComposite) which deals with elements of composition which are
--- a/gfx/layers/ipc/AsyncPanZoomController.cpp
+++ b/gfx/layers/ipc/AsyncPanZoomController.cpp
@@ -101,16 +101,31 @@ static float gYSkateSizeMultiplier = 3.5
 /** The multiplier we apply to a dimension's length if it is stationary. We
  * prefer to increase the size of the Y axis because it is more natural in the
  * case that a user is reading a page that scrolls up/down. Note that one,
  * both or neither of these may be used at any instant.
  */
 static float gXStationarySizeMultiplier = 1.5f;
 static float gYStationarySizeMultiplier = 2.5f;
 
+static TimeStamp sFrameTime;
+
+static TimeStamp
+GetFrameTime() {
+  if (sFrameTime.IsNull()) {
+    return TimeStamp::Now();
+  }
+  return sFrameTime;
+}
+
+void
+AsyncPanZoomController::SetFrameTime(const TimeStamp& aTime) {
+  sFrameTime = aTime;
+}
+
 static void ReadAZPCPrefs()
 {
   Preferences::AddIntVarCache(&gPanRepaintInterval, "gfx.azpc.pan_repaint_interval", gPanRepaintInterval);
   Preferences::AddIntVarCache(&gFlingRepaintInterval, "gfx.azpc.fling_repaint_interval", gFlingRepaintInterval);
   Preferences::AddFloatVarCache(&gMinSkateSpeed, "gfx.azpc.min_skate_speed", gMinSkateSpeed);
   Preferences::AddIntVarCache(&gTouchListenerTimeout, "gfx.azpc.touch_listener_timeout", gTouchListenerTimeout);
   Preferences::AddIntVarCache(&gNumPaintDurationSamples, "gfx.azpc.num_paint_duration_samples", gNumPaintDurationSamples);
   Preferences::AddFloatVarCache(&gTouchStartTolerance, "gfx.azpc.touch_start_tolerance", gTouchStartTolerance);
@@ -149,20 +164,20 @@ AsyncPanZoomController::AsyncPanZoomCont
   :  mGeckoContentController(aGeckoContentController),
      mTouchListenerTimeoutTask(nullptr),
      mX(this),
      mY(this),
      mAllowZoom(true),
      mMinZoom(MIN_ZOOM),
      mMaxZoom(MAX_ZOOM),
      mMonitor("AsyncPanZoomController"),
-     mLastSampleTime(TimeStamp::Now()),
+     mLastSampleTime(GetFrameTime()),
      mState(NOTHING),
-     mPreviousPaintStartTime(TimeStamp::Now()),
-     mLastAsyncScrollTime(TimeStamp::Now()),
+     mPreviousPaintStartTime(GetFrameTime()),
+     mLastAsyncScrollTime(GetFrameTime()),
      mLastAsyncScrollOffset(0, 0),
      mCurrentAsyncScrollOffset(0, 0),
      mAsyncScrollTimeoutTask(nullptr),
      mAsyncScrollThrottleTime(100),
      mAsyncScrollTimeout(300),
      mDPI(72),
      mWaitingForContentToPaint(false),
      mDisableNextTouchBatch(false),
@@ -758,17 +773,17 @@ void AsyncPanZoomController::TrackTouch(
                                                           timeDelta));
     if (fabs(displacement.x) <= EPSILON && fabs(displacement.y) <= EPSILON) {
       return;
     }
 
     ScrollBy(CSSPoint::FromUnknownPoint(displacement));
     ScheduleComposite();
 
-    TimeDuration timePaintDelta = TimeStamp::Now() - mPreviousPaintStartTime;
+    TimeDuration timePaintDelta = GetFrameTime() - mPreviousPaintStartTime;
     if (timePaintDelta.ToMilliseconds() > gPanRepaintInterval) {
       RequestContentRepaint();
     }
   }
 }
 
 SingleTouchData& AsyncPanZoomController::GetFirstSingleTouch(const MultiTouchInput& aEvent) {
   return (SingleTouchData&)aEvent.mTouches[0];
@@ -795,37 +810,34 @@ bool AsyncPanZoomController::DoFling(con
   // We want to inversely scale it because when you're zoomed further in, a
   // larger swipe should move you a shorter distance.
   ScreenToCSSScale inverseResolution = CalculateResolution(mFrameMetrics).Inverse();
 
   ScrollBy(CSSPoint::FromUnknownPoint(gfx::Point(
     mX.GetDisplacementForDuration(inverseResolution.scale, aDelta),
     mY.GetDisplacementForDuration(inverseResolution.scale, aDelta)
   )));
-  TimeDuration timePaintDelta = TimeStamp::Now() - mPreviousPaintStartTime;
+  TimeDuration timePaintDelta = GetFrameTime() - mPreviousPaintStartTime;
   if (timePaintDelta.ToMilliseconds() > gFlingRepaintInterval) {
     RequestContentRepaint();
   }
 
   return true;
 }
 
 void AsyncPanZoomController::CancelAnimation() {
   mState = NOTHING;
 }
 
 void AsyncPanZoomController::SetCompositorParent(CompositorParent* aCompositorParent) {
   mCompositorParent = aCompositorParent;
 }
 
 void AsyncPanZoomController::ScrollBy(const CSSPoint& aOffset) {
-  CSSPoint newOffset = mFrameMetrics.mScrollOffset + aOffset;
-  FrameMetrics metrics(mFrameMetrics);
-  metrics.mScrollOffset = newOffset;
-  mFrameMetrics = metrics;
+  mFrameMetrics.mScrollOffset += aOffset;
 }
 
 void AsyncPanZoomController::ScaleWithFocus(float aZoom,
                                             const ScreenPoint& aFocus) {
   float zoomFactor = aZoom / mFrameMetrics.mZoom.width;
   CSSToScreenScale resolution = CalculateResolution(mFrameMetrics);
 
   SetZoomAndResolution(aZoom);
@@ -993,17 +1005,17 @@ int AsyncPanZoomController::GetDPI() {
 
 void AsyncPanZoomController::ScheduleComposite() {
   if (mCompositorParent) {
     mCompositorParent->ScheduleRenderOnCompositorThread();
   }
 }
 
 void AsyncPanZoomController::RequestContentRepaint() {
-  mPreviousPaintStartTime = TimeStamp::Now();
+  mPreviousPaintStartTime = GetFrameTime();
 
   double estimatedPaintSum = 0.0;
   for (uint32_t i = 0; i < mPreviousPaintDurations.Length(); i++) {
     estimatedPaintSum += mPreviousPaintDurations[i].ToSeconds();
   }
 
   double estimatedPaintDuration = 0.0;
   if (estimatedPaintSum > EPSILON) {
@@ -1202,17 +1214,17 @@ void AsyncPanZoomController::NotifyLayer
   if (mWaitingForContentToPaint) {
     // Remove the oldest sample we have if adding a new sample takes us over our
     // desired number of samples.
     if (mPreviousPaintDurations.Length() >= gNumPaintDurationSamples) {
       mPreviousPaintDurations.RemoveElementAt(0);
     }
 
     mPreviousPaintDurations.AppendElement(
-      TimeStamp::Now() - mPreviousPaintStartTime);
+      GetFrameTime() - mPreviousPaintStartTime);
   } else {
     // No paint was requested, but we got one anyways. One possible cause of this
     // is that content could have fired a scrollTo(). In this case, we should take
     // the new scroll offset. Document/viewport changes are handled elsewhere.
     // Also note that, since NotifyLayersUpdated() is called whenever there's a
     // layers update, we didn't necessarily get a new scroll offset, but we're
     // updating our local copy of it anyways just in case.
     switch (mState) {
@@ -1372,17 +1384,17 @@ void AsyncPanZoomController::ZoomToRect(
     if (zoomToRect.x + rectAfterZoom.width > cssPageRect.width) {
       zoomToRect.x = cssPageRect.width - rectAfterZoom.width;
       zoomToRect.x = zoomToRect.x > 0 ? zoomToRect.x : 0;
     }
 
     mStartZoomToMetrics = mFrameMetrics;
     mEndZoomToMetrics.mScrollOffset = zoomToRect.TopLeft();
 
-    mAnimationStartTime = TimeStamp::Now();
+    mAnimationStartTime = GetFrameTime();
 
     ScheduleComposite();
   }
 }
 
 void AsyncPanZoomController::ContentReceivedTouch(bool aPreventDefault) {
   if (!mFrameMetrics.mMayHaveTouchListeners && !mDelayPanning) {
     mTouchQueue.Clear();
--- a/gfx/layers/ipc/AsyncPanZoomController.h
+++ b/gfx/layers/ipc/AsyncPanZoomController.h
@@ -246,16 +246,23 @@ public:
   void SendAsyncScrollEvent();
 
   /**
    * Handler for events which should not be intercepted by the touch listener.
    * Does the work for ReceiveInputEvent().
    */
   nsEventStatus HandleInputEvent(const InputData& aEvent);
 
+  /**
+   * Sync panning and zooming animation using a fixed frame time.
+   * This will ensure that we animate the APZC correctly with other external
+   * animations to the same timestamp.
+   */
+  static void SetFrameTime(const TimeStamp& aMilliseconds);
+
 protected:
   /**
    * Helper method for touches beginning. Sets everything up for panning and any
    * multitouch gestures.
    */
   nsEventStatus OnTouchStart(const MultiTouchInput& aEvent);
 
   /**
--- a/gfx/src/nsRegion.cpp
+++ b/gfx/src/nsRegion.cpp
@@ -191,16 +191,22 @@ mozilla::ThreadLocal<RgnRectMemoryAlloca
 void RgnRectMemoryAllocatorDTOR(void *priv)
 {
   RgnRectMemoryAllocator* allocator = gRectPoolTlsIndex.get();
   delete allocator;
 }
 
 nsresult nsRegion::InitStatic()
 {
+  if (gRectPoolTlsIndex.initialized()) {
+    // It's ok to call InitStatic if we called ShutdownStatic first
+    MOZ_ASSERT(gRectPoolTlsIndex.get() == nullptr);
+    return NS_OK;
+  }
+
   return gRectPoolTlsIndex.init() ? NS_OK : NS_ERROR_FAILURE;
 }
 
 void nsRegion::ShutdownStatic()
 {
   RgnRectMemoryAllocator* allocator = gRectPoolTlsIndex.get();
   if (!allocator)
     return;
--- a/gfx/tests/gtest/Makefile.in
+++ b/gfx/tests/gtest/Makefile.in
@@ -21,23 +21,25 @@ LOCAL_INCLUDES = \
   -I$(topsrcdir)/gfx/layers \
   -I$(topsrcdir)/gfx/2d \
   -I$(topsrcdir)/gfx/2d/unittest \
   $(NULL)
 
 GTEST_CPPSRCS = \
   TestLayers.cpp \
   TestTiledLayerBuffer.cpp \
+  TestAsyncPanZoomController.cpp \
   $(NULL)
 
 # Because of gkmedia on windows we wont find these
 # symbols in xul.dll.
 ifneq ($(MOZ_WIDGET_TOOLKIT),windows)
 GTEST_CPPSRCS += \
   TestMoz2D.cpp \
   TestBase.cpp \
   TestPoint.cpp \
   TestScaling.cpp \
   $(NULL)
 endif
 
 include $(topsrcdir)/config/rules.mk
+include $(topsrcdir)/ipc/chromium/chromium-config.mk
 
new file mode 100644
--- /dev/null
+++ b/gfx/tests/gtest/TestAsyncPanZoomController.cpp
@@ -0,0 +1,196 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+#include "mozilla/layers/AsyncCompositionManager.h" // for ViewTransform
+#include "mozilla/layers/AsyncPanZoomController.h"
+#include "mozilla/layers/LayerManagerComposite.h"
+#include "mozilla/layers/GeckoContentController.h"
+#include "Layers.h"
+
+#include "gtest/gtest.h"
+#include "gmock/gmock.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::layers;
+using ::testing::_;
+
+class MockContentController : public GeckoContentController {
+public:
+  MOCK_METHOD1(RequestContentRepaint, void(const FrameMetrics&));
+  MOCK_METHOD1(HandleDoubleTap, void(const CSSIntPoint&));
+  MOCK_METHOD1(HandleSingleTap, void(const CSSIntPoint&));
+  MOCK_METHOD1(HandleLongTap, void(const CSSIntPoint&));
+  MOCK_METHOD2(SendAsyncScrollDOMEvent, void(const CSSRect &aContentRect, const CSSSize &aScrollableSize));
+  MOCK_METHOD2(PostDelayedTask, void(Task* aTask, int aDelayMs));
+};
+
+class TestContainerLayer : public ContainerLayer {
+  public:
+    TestContainerLayer()
+      : ContainerLayer(nullptr, nullptr)
+    {}
+  void RemoveChild(Layer* aChild) {}
+  void InsertAfter(Layer* aChild, Layer* aAfter) {}
+  void ComputeEffectiveTransforms(const gfx3DMatrix& aTransformToSurface) {}
+  void RepositionChild(Layer* aChild, Layer* aAfter) {}
+};
+
+static
+FrameMetrics TestFrameMetrics() {
+  FrameMetrics fm;
+
+  fm.mDisplayPort = CSSRect(0, 0, 10, 10);
+  fm.mCompositionBounds = ScreenIntRect(0, 0, 10, 10);
+  fm.mCriticalDisplayPort = CSSRect(0, 0, 10, 10);
+  fm.mScrollableRect = CSSRect(0, 0, 100, 100);
+  fm.mViewport = CSSRect(0, 0, 10, 10);
+
+  return fm;
+}
+
+static
+void ApzcPan(AsyncPanZoomController* apzc, int& aTime, int aTouchStartY, int aTouchEndY) {
+
+  const int TIME_BETWEEN_TOUCH_EVENT = 100;
+  const int OVERCOME_TOUCH_TOLERANCE = 100;
+  MultiTouchInput mti;
+  nsEventStatus status;
+
+  mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_START, aTime);
+  aTime += TIME_BETWEEN_TOUCH_EVENT;
+  // Make sure the move is large enough to not be handled as a tap
+  mti.mTouches.AppendElement(SingleTouchData(0, ScreenIntPoint(10, aTouchStartY+OVERCOME_TOUCH_TOLERANCE), ScreenSize(0, 0), 0, 0));
+  status = apzc->HandleInputEvent(mti);
+  EXPECT_EQ(status, nsEventStatus_eConsumeNoDefault);
+  // APZC should be in TOUCHING state
+
+  mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, aTime);
+  aTime += TIME_BETWEEN_TOUCH_EVENT;
+  mti.mTouches.AppendElement(SingleTouchData(0, ScreenIntPoint(10, aTouchStartY), ScreenSize(0, 0), 0, 0));
+  status = apzc->HandleInputEvent(mti);
+  EXPECT_EQ(status, nsEventStatus_eConsumeNoDefault);
+  // APZC should be in PANNING, otherwise status != ConsumeNoDefault
+
+  mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, aTime);
+  aTime += TIME_BETWEEN_TOUCH_EVENT;
+  mti.mTouches.AppendElement(SingleTouchData(0, ScreenIntPoint(10, aTouchEndY), ScreenSize(0, 0), 0, 0));
+  status = apzc->HandleInputEvent(mti);
+  EXPECT_EQ(status, nsEventStatus_eConsumeNoDefault);
+
+  mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_END, aTime);
+  aTime += TIME_BETWEEN_TOUCH_EVENT;
+  mti.mTouches.AppendElement(SingleTouchData(0, ScreenIntPoint(10, aTouchEndY), ScreenSize(0, 0), 0, 0));
+  status = apzc->HandleInputEvent(mti);
+}
+
+TEST(AsyncPanZoomController, Constructor) {
+  // RefCounted class can't live in the stack
+  nsRefPtr<MockContentController> mcc = new MockContentController();
+  nsRefPtr<AsyncPanZoomController> apzc = new AsyncPanZoomController(mcc);
+  apzc->NotifyLayersUpdated(TestFrameMetrics(), true);
+}
+
+TEST(AsyncPanZoomController, SimpleTransform) {
+  TimeStamp testStartTime = TimeStamp::Now();
+  // RefCounted class can't live in the stack
+  nsRefPtr<MockContentController> mcc = new MockContentController();
+  nsRefPtr<AsyncPanZoomController> apzc = new AsyncPanZoomController(mcc);
+  apzc->NotifyLayersUpdated(TestFrameMetrics(), true);
+
+  TestContainerLayer layer;
+  ScreenPoint pointOut;
+  ViewTransform viewTransformOut;
+  apzc->SampleContentTransformForFrame(testStartTime, &layer, &viewTransformOut, pointOut);
+
+  EXPECT_EQ(pointOut, ScreenPoint());
+  EXPECT_EQ(viewTransformOut, ViewTransform());
+}
+
+TEST(AsyncPanZoomController, Pan) {
+  TimeStamp testStartTime = TimeStamp::Now();
+  AsyncPanZoomController::SetFrameTime(testStartTime);
+
+  nsRefPtr<MockContentController> mcc = new MockContentController();
+  nsRefPtr<AsyncPanZoomController> apzc = new AsyncPanZoomController(mcc);
+  apzc->NotifyLayersUpdated(TestFrameMetrics(), true);
+
+  EXPECT_CALL(*mcc, SendAsyncScrollDOMEvent(_,_)).Times(4);
+  EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(1);
+
+  int time = 0;
+  int touchStart = 50;
+  int touchEnd = 10;
+  TestContainerLayer layer;
+  ScreenPoint pointOut;
+  ViewTransform viewTransformOut;
+
+  // Pan down
+  ApzcPan(apzc, time, touchStart, touchEnd);
+  apzc->SampleContentTransformForFrame(testStartTime, &layer, &viewTransformOut, pointOut);
+  EXPECT_EQ(pointOut, ScreenPoint(0, -(touchEnd-touchStart)));
+  EXPECT_NE(viewTransformOut, ViewTransform());
+
+  // Pan back
+  ApzcPan(apzc, time, touchEnd, touchStart);
+  apzc->SampleContentTransformForFrame(testStartTime, &layer, &viewTransformOut, pointOut);
+  EXPECT_EQ(pointOut, ScreenPoint());
+  EXPECT_EQ(viewTransformOut, ViewTransform());
+}
+
+TEST(AsyncPanZoomController, Fling) {
+  TimeStamp testStartTime = TimeStamp::Now();
+  AsyncPanZoomController::SetFrameTime(testStartTime);
+
+  nsRefPtr<MockContentController> mcc = new MockContentController();
+  nsRefPtr<AsyncPanZoomController> apzc = new AsyncPanZoomController(mcc);
+  apzc->NotifyLayersUpdated(TestFrameMetrics(), true);
+
+  EXPECT_CALL(*mcc, SendAsyncScrollDOMEvent(_,_)).Times(2);
+  EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(1);
+
+  int time = 0;
+  int touchStart = 50;
+  int touchEnd = 10;
+  TestContainerLayer layer;
+  ScreenPoint pointOut;
+  ViewTransform viewTransformOut;
+
+  // Fling down. Each step scroll further down
+  ApzcPan(apzc, time, touchStart, touchEnd);
+  ScreenPoint lastPoint;
+  for (int i = 1; i < 50; i+=1) {
+    apzc->SampleContentTransformForFrame(testStartTime+TimeDuration::FromMilliseconds(i), &layer, &viewTransformOut, pointOut);
+    printf("Time %f, y position %f\n", (float)i, pointOut.y);
+    EXPECT_GT(pointOut.y, lastPoint.y);
+    lastPoint = pointOut;
+  }
+}
+
+TEST(AsyncPanZoomController, OverScrollPanning) {
+  TimeStamp testStartTime = TimeStamp::Now();
+  AsyncPanZoomController::SetFrameTime(testStartTime);
+
+  nsRefPtr<MockContentController> mcc = new MockContentController();
+  nsRefPtr<AsyncPanZoomController> apzc = new AsyncPanZoomController(mcc);
+  apzc->NotifyLayersUpdated(TestFrameMetrics(), true);
+
+  EXPECT_CALL(*mcc, SendAsyncScrollDOMEvent(_,_)).Times(3);
+  EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(1);
+
+  // Pan sufficiently to hit overscroll behavior
+  int time = 0;
+  int touchStart = 500;
+  int touchEnd = 10;
+  TestContainerLayer layer;
+  ScreenPoint pointOut;
+  ViewTransform viewTransformOut;
+
+  // Pan down
+  ApzcPan(apzc, time, touchStart, touchEnd);
+  apzc->SampleContentTransformForFrame(testStartTime+TimeDuration::FromMilliseconds(1000), &layer, &viewTransformOut, pointOut);
+  EXPECT_EQ(pointOut, ScreenPoint(0, 90));
+}
+