Bug 1383365 - Add a test to assert async key scrolling happens. r=botond draft
authorRyan Hunt <rhunt@eqrion.net>
Sun, 23 Jul 2017 12:42:26 -0400
changeset 616527 6824ddf0e0d00134b49147fee21ee4e75455ff29
parent 616526 cb1228dcd6923884e3304c7b51851dee07964706
child 616528 1d3b8483e7afbd53cc7fe3502018f5750230f5d4
child 616855 3d684c76e67c3cbc2434e41f343232d6e6a69366
push id70717
push userbmo:rhunt@eqrion.net
push dateThu, 27 Jul 2017 05:04:58 +0000
reviewersbotond
bugs1383365
milestone56.0a1
Bug 1383365 - Add a test to assert async key scrolling happens. r=botond MozReview-Commit-ID: 13XydDOHXUE
gfx/layers/apz/src/APZCTreeManager.cpp
gfx/layers/apz/src/AsyncPanZoomController.cpp
gfx/layers/apz/src/AsyncPanZoomController.h
gfx/layers/apz/test/mochitest/apz_test_utils.js
gfx/layers/apz/test/mochitest/helper_key_scroll.html
gfx/layers/apz/test/mochitest/mochitest.ini
gfx/layers/apz/test/mochitest/test_key_scroll.html
--- a/gfx/layers/apz/src/APZCTreeManager.cpp
+++ b/gfx/layers/apz/src/APZCTreeManager.cpp
@@ -847,16 +847,18 @@ APZCTreeManager::PrepareNodeForLayer(con
       }
       if (aMetrics.IsRootContent()) {
         aState.mPaintLogger.LogTestData(aMetrics.GetScrollId(),
             "isRootContent", true);
       }
       // Note that the async scroll offset is in ParentLayer pixels
       aState.mPaintLogger.LogTestData(aMetrics.GetScrollId(), "asyncScrollOffset",
           apzc->GetCurrentAsyncScrollOffset(AsyncPanZoomController::eForHitTesting));
+      aState.mPaintLogger.LogTestData(aMetrics.GetScrollId(), "hasAsyncKeyScrolled",
+          apzc->TestHasAsyncKeyScrolled());
     }
 
     if (newApzc) {
       auto it = mZoomConstraints.find(guid);
       if (it != mZoomConstraints.end()) {
         // We have a zoomconstraints for this guid, apply it.
         apzc->UpdateZoomConstraints(it->second);
       } else if (!apzc->HasNoParentWithSameLayersId()) {
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -732,16 +732,17 @@ AsyncPanZoomController::AsyncPanZoomCont
      mOverscrollEffect(MakeUnique<OverscrollEffect>(*this)),
      mState(NOTHING),
      mNotificationBlockers(0),
      mInputQueue(aInputQueue),
      mPinchPaintTimerSet(false),
      mAPZCId(sAsyncPanZoomControllerCount++),
      mSharedLock(nullptr),
      mAsyncTransformAppliedToContent(false),
+     mTestHasAsyncKeyScrolled(false),
      mCheckerboardEventLock("APZCBELock")
 {
   if (aGestures == USE_GESTURE_DETECTOR) {
     mGestureEventListener = new GestureEventListener(this);
   }
 }
 
 AsyncPanZoomController::~AsyncPanZoomController()
@@ -1698,16 +1699,19 @@ void ReportKeyboardScrollAction(const Ke
 }
 
 nsEventStatus
 AsyncPanZoomController::OnKeyboard(const KeyboardInput& aEvent)
 {
   // Report the type of scroll action to telemetry
   ReportKeyboardScrollAction(aEvent.mAction);
 
+  // Mark that this APZC has async key scrolled
+  mTestHasAsyncKeyScrolled = true;
+
   // Calculate the destination for this keyboard scroll action
   CSSPoint destination = GetKeyboardDestination(aEvent.mAction);
   nsIScrollableFrame::ScrollUnit scrollUnit = KeyboardScrollAction::GetScrollUnit(aEvent.mAction.mType);
 
   // The lock must be held across the entire update operation, so the
   // compositor doesn't end the animation before we get a chance to
   // update it.
   ReentrantMonitorAutoEnter lock(mMonitor);
--- a/gfx/layers/apz/src/AsyncPanZoomController.h
+++ b/gfx/layers/apz/src/AsyncPanZoomController.h
@@ -1208,16 +1208,24 @@ private:
 
 
   /* ===================================================================
    * The functions and members in this section are used for testing
    * and assertion purposes only.
    */
 public:
   /**
+   * Gets whether this APZC has performed async key scrolling.
+   */
+  bool TestHasAsyncKeyScrolled() const
+  {
+    return mTestHasAsyncKeyScrolled;
+  }
+
+  /**
    * Set an extra offset for testing async scrolling.
    */
   void SetTestAsyncScrollOffset(const CSSPoint& aPoint)
   {
     mTestAsyncScrollOffset = aPoint;
   }
   /**
    * Set an extra offset for testing async scrolling.
@@ -1244,18 +1252,19 @@ public:
 
 private:
   // Extra offset to add to the async scroll position for testing
   CSSPoint mTestAsyncScrollOffset;
   // Extra zoom to include in the aync zoom for testing
   LayerToParentLayerScale mTestAsyncZoom;
   // Flag to track whether or not the APZ transform is not used. This
   // flag is recomputed for every composition frame.
-  bool mAsyncTransformAppliedToContent;
-
+  bool mAsyncTransformAppliedToContent : 1;
+  // Flag to track whether or not this APZC has ever async key scrolled.
+  bool mTestHasAsyncKeyScrolled : 1;
 
   /* ===================================================================
    * The functions and members in this section are used for checkerboard
    * recording.
    */
 private:
   // Helper function to update the in-progress checkerboard event, if any.
   void UpdateCheckerboardEvent(const MutexAutoLock& aProofOfLock,
--- a/gfx/layers/apz/test/mochitest/apz_test_utils.js
+++ b/gfx/layers/apz/test/mochitest/apz_test_utils.js
@@ -270,16 +270,20 @@ function isApzEnabled() {
     // All tests are required to have at least one assertion. Since APZ is
     // disabled, and the main test is presumably not going to run, we stick in
     // a dummy assertion here to keep the test passing.
     SimpleTest.ok(true, "APZ is not enabled; this test will be skipped");
   }
   return enabled;
 }
 
+function isKeyApzEnabled() {
+  return isApzEnabled() && SpecialPowers.getBoolPref("apz.keyboard.enabled");
+}
+
 // Despite what this function name says, this does not *directly* run the
 // provided continuation testFunction. Instead, it returns a function that
 // can be used to run the continuation. The extra level of indirection allows
 // it to be more easily added to a promise chain, like so:
 //   waitUntilApzStable().then(runContinuation(myTest));
 //
 // If you want to run the continuation directly, outside of a promise chain,
 // you can invoke the return value of this function, like so:
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/helper_key_scroll.html
@@ -0,0 +1,107 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1383365
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Async key scrolling test, helper page</title>
+  <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
+  <script type="application/javascript" src="apz_test_utils.js"></script>
+  <script type="application/javascript">
+    var SimpleTest = window.opener.SimpleTest;
+
+    SimpleTest.waitForFocus(runTests, window);
+
+    // --------------------------------------------------------------------
+    // Async key scrolling test
+    //
+    // This test checks that a key scroll occurs asynchronously.
+    //
+    // The page contains a <div> that is large enough to make the page
+    // scrollable. We first synthesize a page down to scroll to the bottom
+    // of the page. Once we have reached the bottom of the page, we synthesize
+    // a page up to get us back to the top of the page.
+    //
+    // Once at the top, we request test data from APZ, rebuild the APZC tree
+    // structure, and use it to check that async key scrolling happened.
+    // --------------------------------------------------------------------
+
+    function runTests() {
+      // Sanity check
+      SimpleTest.is(checkHasAsyncKeyScrolled(false), false, "expected no async key scrolling before test");
+
+      // Send a key to initiate a page scroll to take us to the bottom of the
+      // page. This scroll is done synchronously because APZ doesn't have
+      // current focus state at page load.
+      window.addEventListener("scroll", waitForScrollBottom);
+      window.synthesizeKey("VK_END", {});
+    };
+
+    function waitForScrollBottom() {
+      if (window.scrollY < window.scrollYMax) {
+        return;
+      }
+      window.removeEventListener("scroll", waitForScrollBottom);
+
+      // Wait for scrolling to finish before dispatching the next key input or
+      // the default action won't occur.
+      waitForApzFlushedRepaints(function () {
+        // This scroll should be asynchronous now that the focus state is up to date.
+        window.addEventListener("scroll", waitForScrollTop);
+        window.synthesizeKey("VK_HOME", {});
+      });
+    };
+
+    function waitForScrollTop() {
+      if (window.scrollY > 0) {
+        return;
+      }
+      window.removeEventListener("scroll", waitForScrollTop);
+
+      // Wait for APZ to settle and then check that async scrolling happened.
+      waitForApzFlushedRepaints(function () {
+        SimpleTest.is(checkHasAsyncKeyScrolled(true), true, "expected async key scrolling after test");
+        window.opener.finishTest();
+      });
+    };
+
+    function checkHasAsyncKeyScrolled(requirePaints) {
+      // Get the compositor-side test data from nsIDOMWindowUtils.
+      var utils = SpecialPowers.getDOMWindowUtils(window);
+      var compositorTestData = utils.getCompositorAPZTestData();
+
+      if (requirePaints) {
+        SimpleTest.ok(compositorTestData.paints.length > 0,
+                      "expected at least one paint in compositor test data");
+      }
+
+      // Get the sequence number of the last paint on the compositor side.
+      // We do this before converting the APZ test data because the conversion
+      // loses the order of the paints.
+      var lastPaint = compositorTestData.paints[compositorTestData.paints.length - 1];
+      var lastPaintSeqNo = lastPaint.sequenceNumber;
+
+      // Convert the test data into a representation that's easier to navigate.
+      compositorTestData = convertTestData(compositorTestData);
+
+      // Reconstruct the APZC tree structure in the last paint.
+      var apzcTree = buildApzcTree(compositorTestData.paints[lastPaintSeqNo]);
+      var rcd = findRcdNode(apzcTree);
+
+      if (rcd) {
+        return rcd.hasAsyncKeyScrolled === "1";
+      } else {
+        SimpleTest.info("Last paint rcd is null");
+        return false;
+      }
+    }
+  </script>
+</head>
+<body style="height: 500px; overflow: scroll">
+  <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1383365">Async key scrolling test</a>
+  <!-- Put enough content into the page to make it have a nonzero scroll range -->
+  <div style="height: 5000px"></div>
+</body>
+</html>
--- a/gfx/layers/apz/test/mochitest/mochitest.ini
+++ b/gfx/layers/apz/test/mochitest/mochitest.ini
@@ -13,16 +13,17 @@
     helper_bug1346632.html
     helper_click.html
     helper_div_pan.html
     helper_drag_click.html
     helper_drag_scroll.html
     helper_iframe_pan.html
     helper_iframe1.html
     helper_iframe2.html
+    helper_key_scroll.html
     helper_long_tap.html
     helper_scroll_inactive_perspective.html
     helper_scroll_inactive_zindex.html
     helper_scroll_on_position_fixed.html
     helper_scroll_over_scrollbar.html
     helper_scrollto_tap.html
     helper_subframe_style.css
     helper_tall.html
@@ -48,16 +49,17 @@
   skip-if = (toolkit == 'android') # mouse events not supported on mobile
 [test_group_pointerevents.html]
 [test_group_touchevents.html]
 [test_group_wheelevents.html]
   skip-if = (toolkit == 'android') # wheel events not supported on mobile
 [test_group_zoom.html]
   skip-if = (toolkit != 'android') # only android supports zoom
 [test_interrupted_reflow.html]
+[test_key_scroll.html]
 [test_layerization.html]
   skip-if = (os == 'android') # wheel events not supported on mobile
 [test_scroll_inactive_bug1190112.html]
   skip-if = (os == 'android') # wheel events not supported on mobile
 [test_scroll_inactive_flattened_frame.html]
   skip-if = (os == 'android') # wheel events not supported on mobile
 [test_scroll_subframe_scrollbar.html]
   skip-if = (os == 'android') # wheel events not supported on mobile
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_key_scroll.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1383365
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Async key scrolling test</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="apz_test_utils.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="application/javascript">
+
+    if (isKeyApzEnabled()) {
+      SimpleTest.waitForExplicitFinish();
+
+      // Run the actual test in its own window, because it requires that the
+      // root APZC be scrollable. Mochitest pages themselves often run
+      // inside an iframe which means we have no control over the root APZC.
+      var w = null;
+      window.onload = function() {
+        pushPrefs([["apz.test.logging_enabled", true],
+                   ["test.events.async.enabled", true]]).then(function() {
+          w = window.open("helper_key_scroll.html", "_blank");
+        });
+      };
+    } else {
+      SimpleTest.ok(true, "Keyboard APZ is disabled");
+    }
+
+    function finishTest() {
+      w.close();
+      SimpleTest.finish();
+    };
+
+  </script>
+</head>
+<body>
+  <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1383365">Async key scrolling test</a>
+</body>
+</html>