Bug 1434846 - Factor out some utility functions for testing APZ scrollbar hit testing. r=kats
authorBotond Ballo <botond@mozilla.com>
Wed, 14 Feb 2018 16:06:53 -0500
changeset 403884 0e1283be874466cfa5e154d9b3642ffce716913a
parent 403883 8eb7b1aa7dde8d24cae4c442ba8d737c7b5f88b8
child 403885 f7c315627ca4a8b009a94369a3d61c96d0ffe6f6
push id99885
push userapavel@mozilla.com
push dateThu, 15 Feb 2018 10:38:09 +0000
treeherdermozilla-inbound@99495614cba7 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskats
bugs1434846
milestone60.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 1434846 - Factor out some utility functions for testing APZ scrollbar hit testing. r=kats MozReview-Commit-ID: G6Q4ryQjzyY
gfx/layers/apz/test/mochitest/apz_test_utils.js
gfx/layers/apz/test/mochitest/helper_hittest_basic.html
--- a/gfx/layers/apz/test/mochitest/apz_test_utils.js
+++ b/gfx/layers/apz/test/mochitest/apz_test_utils.js
@@ -400,8 +400,142 @@ function injectScript(aScript, aWindow =
         dump('Script [' + aScript + '] errored out\n');
         reject();
       };
       e.src = aScript;
       aWindow.document.getElementsByTagName('head')[0].appendChild(e);
     });
   };
 }
+
+// Compute some configuration information used for hit testing.
+// The computed information is cached to avoid recomputing it
+// each time this function is called.
+// The computed information is an object with three fields:
+//   utils: the nsIDOMWindowUtils instance for this window
+//   isWebRender: true if WebRender is enabled
+//   isWindow: true if the platform is Windows
+function getHitTestConfig() {
+  if (!("hitTestConfig" in window)) {
+    var utils = SpecialPowers.getDOMWindowUtils(window);
+    var isWebRender = (utils.layerManagerType == 'WebRender');
+    var isWindows = (getPlatform() == 'windows');
+    window.hitTestConfig = { utils, isWebRender, isWindows };
+  }
+  return window.hitTestConfig;
+}
+
+// Peform a compositor hit test at the given point and return the result.
+// The returned object has two fields:
+//   hitInfo: a combination of APZHitResultFlags
+//   scrollId: the view-id of the scroll frame that was hit
+function hitTest(point) {
+  var utils = getHitTestConfig().utils;
+  dump("Hit-testing point (" + point.x + ", " + point.y + ")\n");
+  utils.sendMouseEvent("MozMouseHittest", point.x, point.y, 0, 0, 0, true, 0, 0, true, true);
+  var data = utils.getCompositorAPZTestData();
+  ok(data.hitResults.length >= 1, "Expected at least one hit result in the APZTestData");
+  var result = data.hitResults[data.hitResults.length - 1];
+  return { hitInfo: result.hitResult, scrollId: result.scrollId };
+}
+
+// Symbolic constants used by hitTestScrollbar().
+var ScrollbarTrackLocation = {
+    START: 1,
+    END: 2
+};
+var LayerState = {
+    ACTIVE: 1,
+    INACTIVE: 2
+};
+
+// Perform a hit test on the scrollbar(s) of a scroll frame.
+// This function takes a single argument which is expected to be
+// an object with the following fields:
+//   element: The scroll frame to perform the hit test on.
+//   directions: The direction(s) of scrollbars to test.
+//     If directions.vertical is true, the vertical scrollbar will be tested.
+//     If directions.horizontal is true, the horizontal scrollbar will be tested.
+//     Both may be true in a single call (in which case two tests are performed).
+//   expectedScrollId: The scroll id that is expected to be hit.
+//   trackLocation: One of ScrollbarTrackLocation.{START, END}.
+//     Determines which end of the scrollbar track is targeted.
+//   expectThumb: Whether the scrollbar thumb is expected to be present
+//     at the targeted end of the scrollbar track.
+//   layerState: Whether the scroll frame is active or inactive.
+// The function performs the hit tests and asserts that the returned
+// hit test information is consistent with the passed parameters.
+// There is no return value.
+// Tests that use this function must set the pref
+// "layout.scrollbars.always-layerize-track".
+function hitTestScrollbar(params) {
+  var config = getHitTestConfig();
+
+  var elem = params.element;
+
+  var boundingClientRect = elem.getBoundingClientRect();
+
+  var verticalScrollbarWidth = boundingClientRect.width - elem.clientWidth;
+  var horizontalScrollbarHeight = boundingClientRect.height - elem.clientHeight;
+
+  // On windows, the scrollbar tracks have buttons on the end. When computing
+  // coordinates for hit-testing we need to account for this. We assume the
+  // buttons are square, and so can use the scrollbar width/height to estimate
+  // the size of the buttons
+  var scrollbarArrowButtonHeight = config.isWindows ? verticalScrollbarWidth : 0;
+  var scrollbarArrowButtonWidth = config.isWindows ? horizontalScrollbarHeight : 0;
+
+  // Compute the expected hit result flags.
+  // The direction flag (APZHitResultFlags.SCROLLBAR_VERTICAL) is added in
+  // later, for the vertical test only.
+  // The APZHitResultFlags.SCROLLBAR flag will be present regardless of whether
+  // the layer is active or inactive because we force layerization of scrollbar
+  // tracks. Unfortunately not forcing the layerization results in different
+  // behaviour on different platforms which makes testing harder.
+  var expectedHitInfo = APZHitResultFlags.VISIBLE | APZHitResultFlags.SCROLLBAR;
+  if (params.expectThumb) {
+    // WebRender will hit-test scroll thumbs even inside inactive scrollframes,
+    // because the hit-test is based on display items and we do in fact generate
+    // the display items for the scroll thumb. The user-observed behaviour is
+    // going to be unaffected because the dispatch-to-content flag will also be
+    // set on these thumbs so it's not like APZ will allow async-scrolling them
+    // before the scrollframe has been activated/layerized. In non-WebRender we
+    // do not generate the layers for thumbs on inactive scrollframes, so the
+    // hit test will be accordingly different.
+    expectedHitInfo |= APZHitResultFlags.DISPATCH_TO_CONTENT;
+    if (config.isWebRender || params.layerState == LayerState.ACTIVE) {
+        expectedHitInfo |= APZHitResultFlags.SCROLLBAR_THUMB;
+    }
+  }
+
+  var scrollframeMsg = (params.layerState == LayerState.ACTIVE)
+    ? "active scrollframe" : "inactive scrollframe";
+
+  // Hit-test the targeted areas, assuming we don't have overlay scrollbars
+  // with zero dimensions.
+  if (params.directions.vertical && verticalScrollbarWidth > 0) {
+    var verticalScrollbarPoint = {
+        x: boundingClientRect.right - (verticalScrollbarWidth / 2),
+        y: (params.trackLocation == ScrollbarTrackLocation.START)
+             ? (boundingClientRect.y + scrollbarArrowButtonHeight + 5)
+             : (boundingClientRect.bottom - horizontalScrollbarHeight - scrollbarArrowButtonHeight - 5)
+    };
+    var {hitInfo, scrollId} = hitTest(verticalScrollbarPoint);
+    is(hitInfo, expectedHitInfo | APZHitResultFlags.SCROLLBAR_VERTICAL,
+       scrollframeMsg + " - vertical scrollbar hit info");
+    is(scrollId, params.expectedScrollId,
+       scrollframeMsg + " - vertical scrollbar scrollid");
+  }
+
+  if (params.directions.horizontal && horizontalScrollbarHeight > 0) {
+    var horizontalScrollbarPoint = {
+        x: (params.trackLocation == ScrollbarTrackLocation.START)
+             ? (boundingClientRect.x + scrollbarArrowButtonWidth + 5)
+             : (boundingClientRect.right - verticalScrollbarWidth - scrollbarArrowButtonWidth - 5),
+        y: boundingClientRect.bottom - (horizontalScrollbarHeight / 2),
+    };
+    var {hitInfo, scrollId} = hitTest(horizontalScrollbarPoint);
+    is(hitInfo, expectedHitInfo,
+       scrollframeMsg + " - horizontal scrollbar hit info");
+    is(scrollId, params.expectedScrollId,
+       scrollframeMsg + " - horizontal scrollbar scrollid");
+  }
+}
--- a/gfx/layers/apz/test/mochitest/helper_hittest_basic.html
+++ b/gfx/layers/apz/test/mochitest/helper_hittest_basic.html
@@ -12,186 +12,126 @@
   <div id="contents" style="width: 500px; height: 500px; background-image: linear-gradient(blue,red)">
    <div id="apzaware" style="position: relative; width: 100px; height: 100px; top: 300px; background-color: red" onwheel="return false;"></div>
   </div>
  </div>
  <div id="make_root_scrollable" style="height: 5000px"></div>
 </body>
 <script type="application/javascript">
 
-var utils = SpecialPowers.getDOMWindowUtils(window);
-var isWebRender = (utils.layerManagerType == 'WebRender');
-var isWindows = (getPlatform() == 'windows');
-
 function centerOf(element) {
   var bounds = element.getBoundingClientRect();
   return { x: bounds.x + (bounds.width / 2), y: bounds.y + (bounds.height / 2) };
 }
 
-function hitTest(point) {
-  dump("Hit-testing point (" + point.x + ", " + point.y + ")\n");
-  utils.sendMouseEvent("MozMouseHittest", point.x, point.y, 0, 0, 0, true, 0, 0, true, true);
-  var data = utils.getCompositorAPZTestData();
-  ok(data.hitResults.length >= 1, "Expected at least one hit result in the APZTestData");
-  var result = data.hitResults[data.hitResults.length - 1];
-  return { hitInfo: result.hitResult, scrollId: result.scrollId };
-}
+function* test(testDriver) {
+  var config = getHitTestConfig();
+  var utils = config.utils;
 
-function* test(testDriver) {
   var scroller = document.getElementById('scroller');
   var apzaware = document.getElementById('apzaware');
 
-  var verticalScrollbarWidth = scroller.getBoundingClientRect().width - scroller.clientWidth;
-  var horizontalScrollbarHeight = scroller.getBoundingClientRect().height - scroller.clientHeight;
-
-  // On windows, the scrollbar tracks have buttons on the end. When computing
-  // coordinates for hit-testing we need to account for this. We assume the
-  // buttons are square, and so can use the scrollbar width/height to estimate
-  // the size of the buttons
-  var scrollbarArrowButtonHeight = isWindows ? verticalScrollbarWidth : 0;
-  var scrollbarArrowButtonWidth = isWindows ? horizontalScrollbarHeight : 0;
-
-  // WebRender will hit-test scroll thumbs even inside inactive scrollframes,
-  // because the hit-test is based on display items and we do in fact generate
-  // the display items for the scroll thumb. The user-observed behaviour is
-  // going to be unaffected because the dispatch-to-content flag will also be
-  // set on these thumbs so it's not like APZ will allow async-scrolling them
-  // before the scrollframe has been activated/layerized. In non-WebRender we
-  // do not generate the layers for thumbs on inactive scrollframes, so the
-  // hit test will be accordingly different.
-  var inactiveScrollframeThumbFlag = isWebRender ? APZHitResultFlags.SCROLLBAR_THUMB : 0;
-
   var {hitInfo, scrollId} = hitTest(centerOf(scroller));
   is(hitInfo, APZHitResultFlags.VISIBLE | APZHitResultFlags.DISPATCH_TO_CONTENT,
      "inactive scrollframe hit info");
   is(scrollId, utils.getViewId(document.scrollingElement),
      "inactive scrollframe scrollid");
 
   // The apz-aware div (which has a non-passive wheel listener) is not visible
   // and so the hit-test should just return the root scrollframe area that's
   // covering it
   var {hitInfo, scrollId} = hitTest(centerOf(apzaware));
   is(hitInfo, APZHitResultFlags.VISIBLE,
      "inactive scrollframe - apzaware block hit info");
   is(scrollId, utils.getViewId(document.scrollingElement),
      "inactive scrollframe - apzaware block scrollid");
 
-  // Hit-test against where the scrollthumbs should be, assuming we don't have
-  // overlay scrollbars with zero dimensions. Note that the scrollframe is still
-  // inactive so the result should just be the same dispatch-to-content area
-  // as before, but because we force layerization of scrollbar tracks the hit
-  // result will have HITTEST_SCROLLBAR set. Unfortunately not forcing the
-  // layerization results in different behaviour on different platforms which
-  // makes testing harder.
-  if (verticalScrollbarWidth > 0) {
-    var verticalScrollbarPoint = {
-        x: scroller.getBoundingClientRect().right - (verticalScrollbarWidth / 2),
-        y: scroller.getBoundingClientRect().y + scrollbarArrowButtonHeight + 5,
-    };
-    var {hitInfo, scrollId} = hitTest(verticalScrollbarPoint);
-    is(hitInfo, APZHitResultFlags.VISIBLE | APZHitResultFlags.DISPATCH_TO_CONTENT
-              | APZHitResultFlags.SCROLLBAR | APZHitResultFlags.SCROLLBAR_VERTICAL
-              | inactiveScrollframeThumbFlag,
-       "inactive scrollframe - vertical scrollbar hit info");
-    is(scrollId, utils.getViewId(document.scrollingElement),
-       "inactive scrollframe - vertical scrollbar scrollid");
-  }
-
-  if (horizontalScrollbarHeight > 0) {
-    var horizontalScrollbarPoint = {
-        x: scroller.getBoundingClientRect().x + scrollbarArrowButtonWidth + 5,
-        y: scroller.getBoundingClientRect().bottom - (horizontalScrollbarHeight / 2),
-    };
-    var {hitInfo, scrollId} = hitTest(horizontalScrollbarPoint);
-    is(hitInfo, APZHitResultFlags.VISIBLE | APZHitResultFlags.DISPATCH_TO_CONTENT
-              | APZHitResultFlags.SCROLLBAR | inactiveScrollframeThumbFlag,
-       "inactive scrollframe - horizontal scrollbar hit info");
-    is(scrollId, utils.getViewId(document.scrollingElement),
-       "inactive scrollframe - horizontal scrollbar scrollid");
-  }
-
+  // Hit test where the scroll thumbs should be.
+  hitTestScrollbar({
+    element: scroller,
+    directions: { vertical: true, horizontal: true },
+    expectedScrollId: utils.getViewId(document.scrollingElement),
+    trackLocation: ScrollbarTrackLocation.START,
+    expectThumb: true,
+    layerState: LayerState.INACTIVE
+  });
 
   // activate the scrollframe but keep the main-thread scroll position at 0.
   // also apply a async scroll offset in the y-direction such that the
   // scrollframe scrolls to the bottom of its range.
   utils.setDisplayPortForElement(0, 0, 500, 500, scroller, 1);
   yield waitForAllPaints(testDriver);
   var scrollY = scroller.scrollTopMax;
   utils.setAsyncScrollOffset(scroller, 0, scrollY);
-  if (isWebRender) {
+  if (config.isWebRender) {
     // Tick the refresh driver once to make sure the compositor has applied the
     // async scroll offset (for APZ hit-testing this doesn't matter, but for
     // WebRender hit-testing we need to make sure WR has the latest info).
     utils.advanceTimeAndRefresh(16);
     utils.restoreNormalRefresh();
   }
 
+  var scrollerViewId = utils.getViewId(scroller);
+
   // Now we again test the middle of the scrollframe, which is now active
   var {hitInfo, scrollId} = hitTest(centerOf(scroller));
   is(hitInfo, APZHitResultFlags.VISIBLE,
      "active scrollframe hit info");
-  is(scrollId, utils.getViewId(scroller),
+  is(scrollId, scrollerViewId,
      "active scrollframe scrollid");
 
   // Test the apz-aware block
   var apzawarePosition = centerOf(apzaware); // main thread position
   apzawarePosition.y -= scrollY; // APZ position
   var {hitInfo, scrollId} = hitTest(apzawarePosition);
   is(hitInfo, APZHitResultFlags.VISIBLE | APZHitResultFlags.DISPATCH_TO_CONTENT,
      "active scrollframe - apzaware block hit info");
-  is(scrollId, utils.getViewId(scroller),
+  is(scrollId, scrollerViewId,
      "active scrollframe - apzaware block scrollid");
 
   // Test the scrollbars. Note that this time the vertical scrollthumb is
   // going to be at the bottom of the track. We'll test both the top and the
   // bottom.
-  if (verticalScrollbarWidth > 0) {
-    // top of scrollbar track
-    var verticalScrollbarPoint = {
-        x: scroller.getBoundingClientRect().right - (verticalScrollbarWidth / 2),
-        y: scroller.getBoundingClientRect().top + scrollbarArrowButtonHeight + 5,
-    };
-    var {hitInfo, scrollId} = hitTest(verticalScrollbarPoint);
-    is(hitInfo, APZHitResultFlags.VISIBLE | APZHitResultFlags.SCROLLBAR
-              | APZHitResultFlags.SCROLLBAR_VERTICAL,
-       "active scrollframe - vertical scrollbar hit info");
-    is(scrollId, utils.getViewId(scroller),
-       "active scrollframe - vertical scrollbar scrollid");
 
-    // bottom of scrollbar track (scrollthumb)
-    verticalScrollbarPoint.y = scroller.getBoundingClientRect().bottom - horizontalScrollbarHeight - scrollbarArrowButtonHeight - 5;
-    var {hitInfo, scrollId} = hitTest(verticalScrollbarPoint);
-    is(hitInfo, APZHitResultFlags.VISIBLE | APZHitResultFlags.DISPATCH_TO_CONTENT
-              | APZHitResultFlags.SCROLLBAR | APZHitResultFlags.SCROLLBAR_THUMB
-              | APZHitResultFlags.SCROLLBAR_VERTICAL,
-       "active scrollframe - vertical scrollthumb hit info");
-    is(scrollId, utils.getViewId(scroller),
-       "active scrollframe - vertical scrollthumb scrollid");
-  }
-  if (horizontalScrollbarHeight > 0) {
-    // left part of scrollbar track (has scrollthumb)
-    var horizontalScrollbarPoint = {
-        x: scroller.getBoundingClientRect().x + scrollbarArrowButtonWidth + 5,
-        y: scroller.getBoundingClientRect().bottom - (horizontalScrollbarHeight / 2),
-    };
-    var {hitInfo, scrollId} = hitTest(horizontalScrollbarPoint);
-    is(hitInfo, APZHitResultFlags.VISIBLE | APZHitResultFlags.DISPATCH_TO_CONTENT
-              | APZHitResultFlags.SCROLLBAR | APZHitResultFlags.SCROLLBAR_THUMB,
-       "active scrollframe - horizontal scrollthumb hit info");
-    is(scrollId, utils.getViewId(scroller),
-       "active scrollframe - horizontal scrollthumb scrollid");
-
-    // right part of scrollbar track
-    horizontalScrollbarPoint.x = scroller.getBoundingClientRect().right - verticalScrollbarWidth - scrollbarArrowButtonWidth - 5;
-    var {hitInfo, scrollId} = hitTest(horizontalScrollbarPoint);
-    is(hitInfo, APZHitResultFlags.VISIBLE | APZHitResultFlags.SCROLLBAR,
-       "active scrollframe - horizontal scrollbar hit info");
-    is(scrollId, utils.getViewId(scroller),
-       "active scrollframe - horizontal scrollbar scrollid");
-  }
+  // top of scrollbar track
+  hitTestScrollbar({
+    element: scroller,
+    directions: { vertical: true },
+    expectedScrollId: scrollerViewId,
+    trackLocation: ScrollbarTrackLocation.START,
+    expectThumb: false,
+    layerState: LayerState.ACTIVE
+  });
+  // bottom of scrollbar track (scrollthumb)
+  hitTestScrollbar({
+    element: scroller,
+    directions: { vertical: true },
+    expectedScrollId: scrollerViewId,
+    trackLocation: ScrollbarTrackLocation.END,
+    expectThumb: true,
+    layerState: LayerState.ACTIVE
+  });
+  // left part of scrollbar track (has scrollthumb)
+  hitTestScrollbar({
+    element: scroller,
+    directions: { horizontal: true },
+    expectedScrollId: scrollerViewId,
+    trackLocation: ScrollbarTrackLocation.START,
+    expectThumb: true,
+    layerState: LayerState.ACTIVE
+  });
+  // right part of scrollbar track
+  hitTestScrollbar({
+    element: scroller,
+    directions: { horizontal: true },
+    expectedScrollId: scrollerViewId,
+    trackLocation: ScrollbarTrackLocation.END,
+    expectThumb: false,
+    layerState: LayerState.ACTIVE
+  });
 
   subtestDone();
 }
 
 waitUntilApzStable().then(runContinuation(test));
 
 </script>
 </html>