Bug 1561698 - Check the layers id in hit-testing mochitests as well. r=botond,hsivonen
authorKartikaya Gupta <kgupta@mozilla.com>
Mon, 01 Jul 2019 07:12:24 +0000
changeset 543608 430451f5bf5d15c6ae158ddc66d72bce7c3bb1a5
parent 543607 340f71c8f1dc1d79a6c5976ada31151563f06244
child 543609 34b35632f194e4e711d74bce6415dddb34cebf42
push id2131
push userffxbld-merge
push dateMon, 26 Aug 2019 18:30:20 +0000
treeherdermozilla-release@b19ffb3ca153 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbotond, hsivonen
bugs1561698
milestone69.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 1561698 - Check the layers id in hit-testing mochitests as well. r=botond,hsivonen This exposes the layers id on the APZ hit result testing API, and updates callers to check the layers id is correct. This is mostly unnecessary for these tests, but introduces machinery that will be useful in Fission-enabled tests, where iframes may be OOP and have different layers ids. Differential Revision: https://phabricator.services.mozilla.com/D36146
dom/base/nsDOMWindowUtils.cpp
dom/interfaces/base/nsIDOMWindowUtils.idl
dom/webidl/APZTestData.webidl
gfx/layers/apz/src/APZCTreeManager.cpp
gfx/layers/apz/test/mochitest/apz_test_utils.js
gfx/layers/apz/test/mochitest/helper_hittest_basic.html
gfx/layers/apz/test/mochitest/helper_hittest_checkerboard.html
gfx/layers/apz/test/mochitest/helper_hittest_clippath.html
gfx/layers/apz/test/mochitest/helper_hittest_clipped_fixed_modal.html
gfx/layers/apz/test/mochitest/helper_hittest_fixed_in_scrolled_transform.html
gfx/layers/apz/test/mochitest/helper_hittest_float_bug1434846.html
gfx/layers/apz/test/mochitest/helper_hittest_float_bug1443518.html
gfx/layers/apz/test/mochitest/helper_hittest_nested_transforms_bug1459696.html
gfx/layers/apz/test/mochitest/helper_hittest_pointerevents_svg.html
gfx/layers/apz/test/mochitest/helper_hittest_sticky_bug1478304.html
gfx/layers/apz/test/mochitest/helper_hittest_touchaction.html
gfx/layers/apz/testutil/APZTestData.cpp
gfx/layers/apz/testutil/APZTestData.h
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -4181,8 +4181,22 @@ nsDOMWindowUtils::IsCssPropertyRecordedI
     return NS_ERROR_FAILURE;
   }
 
   bool knownProp = false;
   *aRecorded = Servo_IsCssPropertyRecordedInUseCounter(
       doc->GetStyleUseCounters(), &aPropName, &knownProp);
   return knownProp ? NS_OK : NS_ERROR_FAILURE;
 }
+
+NS_IMETHODIMP
+nsDOMWindowUtils::GetLayersId(uint64_t* aOutLayersId) {
+  nsIWidget* widget = GetWidget();
+  if (!widget) {
+    return NS_ERROR_FAILURE;
+  }
+  BrowserChild* child = widget->GetOwningBrowserChild();
+  if (!child) {
+    return NS_ERROR_FAILURE;
+  }
+  *aOutLayersId = (uint64_t)child->GetLayersId();
+  return NS_OK;
+}
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl
+++ b/dom/interfaces/base/nsIDOMWindowUtils.idl
@@ -2005,16 +2005,18 @@ interface nsIDOMWindowUtils : nsISupport
   const long MOUSE_BUTTONS_NOT_SPECIFIED = -1;
 
   // Return values for getDirectionFromText().
   const long DIRECTION_LTR = 0;
   const long DIRECTION_RTL = 1;
   const long DIRECTION_NOT_SET = 2;
 
   void syncFlushCompositor();
+
+  unsigned long long getLayersId();
 };
 
 [scriptable, uuid(c694e359-7227-4392-a138-33c0cc1f15a6)]
 interface nsITranslationNodeList : nsISupports {
   readonly attribute unsigned long length;
   Node item(in unsigned long index);
 
   // A translation root is a block element, or an inline element
--- a/dom/webidl/APZTestData.webidl
+++ b/dom/webidl/APZTestData.webidl
@@ -47,16 +47,17 @@ namespace APZHitResultFlags {
   const unsigned short SCROLLBAR_VERTICAL = 0x0400;
   const unsigned short REQUIRES_TARGET_CONFIRMATION = 0x0800;
 };
 
 dictionary APZHitResult {
   float screenX;
   float screenY;
   unsigned short hitResult; // combination of the APZHitResultFlags.* flags
+  unsigned long long layersId;
   unsigned long long scrollId;
 };
 
 dictionary AdditionalDataEntry {
   DOMString key;
   DOMString value;
 };
 
--- a/gfx/layers/apz/src/APZCTreeManager.cpp
+++ b/gfx/layers/apz/src/APZCTreeManager.cpp
@@ -1309,17 +1309,17 @@ nsEventStatus APZCTreeManager::ReceiveIn
         if (StaticPrefs::apz_test_logging_enabled() &&
             mouseInput.mType == MouseInput::MOUSE_HITTEST) {
           ScrollableLayerGuid guid = apzc->GetGuid();
 
           MutexAutoLock lock(mTestDataLock);
           auto it = mTestData.find(guid.mLayersId);
           MOZ_ASSERT(it != mTestData.end());
           it->second->RecordHitResult(mouseInput.mOrigin, hitResult,
-                                      guid.mScrollId);
+                                      guid.mLayersId, guid.mScrollId);
         }
 
         TargetConfirmationFlags confFlags{hitResult};
         bool apzDragEnabled = StaticPrefs::apz_drag_enabled();
         if (apzDragEnabled && hitScrollbar) {
           // If scrollbar dragging is enabled and we hit a scrollbar, wait
           // for the main-thread confirmation because it contains drag metrics
           // that we need.
--- a/gfx/layers/apz/test/mochitest/apz_test_utils.js
+++ b/gfx/layers/apz/test/mochitest/apz_test_utils.js
@@ -665,17 +665,17 @@ function centerOf(element) {
 //   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 };
+  return { hitInfo: result.hitResult, scrollId: result.scrollId, layersId: result.layersId };
 }
 
 // Returns a canonical stringification of the hitInfo bitfield.
 function hitInfoToString(hitInfo) {
   var strs = [];
   for (var flag in APZHitResultFlags) {
     if ((hitInfo & APZHitResultFlags[flag]) != 0) {
       strs.push(flag);
@@ -688,19 +688,20 @@ function hitInfoToString(hitInfo) {
     return APZHitResultFlags[a] - APZHitResultFlags[b];
   });
   return strs.join(" | ");
 }
 
 // Takes an object returned by hitTest, along with the expected values, and
 // asserts that they match. Notably, it uses hitInfoToString to provide a
 // more useful message for the case that the hit info doesn't match
-function checkHitResult(hitResult, expectedHitInfo, expectedScrollId, desc) {
+function checkHitResult(hitResult, expectedHitInfo, expectedScrollId, expectedLayersId, desc) {
   is(hitInfoToString(hitResult.hitInfo), hitInfoToString(expectedHitInfo), desc + " hit info");
   is(hitResult.scrollId, expectedScrollId, desc + " scrollid");
+  is(hitResult.layersId, expectedLayersId, desc + " layersid");
 }
 
 // Symbolic constants used by hitTestScrollbar().
 var ScrollbarTrackLocation = {
     START: 1,
     END: 2,
 };
 var LayerState = {
@@ -712,16 +713,17 @@ var LayerState = {
 // 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.
+//   expectedLayersId: The layers 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.
@@ -782,29 +784,31 @@ function hitTestScrollbar(params) {
         x: boundingClientRect.right - (verticalScrollbarWidth / 2),
         y: (params.trackLocation == ScrollbarTrackLocation.START)
              ? (boundingClientRect.y + scrollbarArrowButtonHeight + 5)
              : (boundingClientRect.bottom - horizontalScrollbarHeight - scrollbarArrowButtonHeight - 5),
     };
     checkHitResult(hitTest(verticalScrollbarPoint),
                    expectedHitInfo | APZHitResultFlags.SCROLLBAR_VERTICAL,
                    params.expectedScrollId,
+                   params.expectedLayersId,
                    scrollframeMsg + " - vertical scrollbar");
   }
 
   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),
     };
     checkHitResult(hitTest(horizontalScrollbarPoint),
                    expectedHitInfo,
                    params.expectedScrollId,
+                   params.expectedLayersId,
                    scrollframeMsg + " - horizontal scrollbar");
   }
 }
 
 // Return a list of prefs for the given test identifier.
 function getPrefs(ident) {
   switch (ident) {
     case "TOUCH_EVENTS:PAN":
--- a/gfx/layers/apz/test/mochitest/helper_hittest_basic.html
+++ b/gfx/layers/apz/test/mochitest/helper_hittest_basic.html
@@ -24,31 +24,34 @@ function* test(testDriver) {
   var scroller = document.getElementById("scroller");
   var apzaware = document.getElementById("apzaware");
 
   checkHitResult(hitTest(centerOf(scroller)),
                  APZHitResultFlags.VISIBLE |
                  (config.isWebRender ? APZHitResultFlags.INACTIVE_SCROLLFRAME
                                      : APZHitResultFlags.IRREGULAR_AREA),
                  utils.getViewId(document.scrollingElement),
+                 utils.getLayersId(),
                  "inactive scrollframe");
 
   // 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
   checkHitResult(hitTest(centerOf(apzaware)),
                  APZHitResultFlags.VISIBLE,
                  utils.getViewId(document.scrollingElement),
+                 utils.getLayersId(),
                  "inactive scrollframe - apzaware block");
 
   // Hit test where the scroll thumbs should be.
   hitTestScrollbar({
     element: scroller,
     directions: { vertical: true, horizontal: true },
     expectedScrollId: utils.getViewId(document.scrollingElement),
+    expectedLayersId: utils.getLayersId(),
     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.
@@ -65,64 +68,70 @@ function* test(testDriver) {
   }
 
   var scrollerViewId = utils.getViewId(scroller);
 
   // Now we again test the middle of the scrollframe, which is now active
   checkHitResult(hitTest(centerOf(scroller)),
                  APZHitResultFlags.VISIBLE,
                  scrollerViewId,
+                 utils.getLayersId(),
                  "active scrollframe");
 
   // Test the apz-aware block
   var apzawarePosition = centerOf(apzaware); // main thread position
   apzawarePosition.y -= scrollY; // APZ position
   checkHitResult(hitTest(apzawarePosition),
                  APZHitResultFlags.VISIBLE |
                  (config.isWebRender ? APZHitResultFlags.APZ_AWARE_LISTENERS
                                      : APZHitResultFlags.IRREGULAR_AREA),
                  scrollerViewId,
+                 utils.getLayersId(),
                  "active scrollframe - apzaware block");
 
   // 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.
 
   // top of scrollbar track
   hitTestScrollbar({
     element: scroller,
     directions: { vertical: true },
     expectedScrollId: scrollerViewId,
+    expectedLayersId: utils.getLayersId(),
     trackLocation: ScrollbarTrackLocation.START,
     expectThumb: false,
     layerState: LayerState.ACTIVE,
   });
   // bottom of scrollbar track (scrollthumb)
   hitTestScrollbar({
     element: scroller,
     directions: { vertical: true },
     expectedScrollId: scrollerViewId,
+    expectedLayersId: utils.getLayersId(),
     trackLocation: ScrollbarTrackLocation.END,
     expectThumb: true,
     layerState: LayerState.ACTIVE,
   });
   // left part of scrollbar track (has scrollthumb)
   hitTestScrollbar({
     element: scroller,
     directions: { horizontal: true },
     expectedScrollId: scrollerViewId,
+    expectedLayersId: utils.getLayersId(),
     trackLocation: ScrollbarTrackLocation.START,
     expectThumb: true,
     layerState: LayerState.ACTIVE,
   });
   // right part of scrollbar track
   hitTestScrollbar({
     element: scroller,
     directions: { horizontal: true },
     expectedScrollId: scrollerViewId,
+    expectedLayersId: utils.getLayersId(),
     trackLocation: ScrollbarTrackLocation.END,
     expectThumb: false,
     layerState: LayerState.ACTIVE,
   });
 
   subtestDone();
 }
 
--- a/gfx/layers/apz/test/mochitest/helper_hittest_checkerboard.html
+++ b/gfx/layers/apz/test/mochitest/helper_hittest_checkerboard.html
@@ -42,16 +42,17 @@ function* test(testDriver) {
   var scrollerViewId = utils.getViewId(scroller);
 
   // Hit-test the middle of the scrollframe, which is now inside the
   // checkerboarded region, and check that we hit the scrollframe and
   // not its parent.
   checkHitResult(hitTest(centerOf(scroller)),
                  APZHitResultFlags.VISIBLE,
                  scrollerViewId,
+                 utils.getLayersId(),
                  "active scrollframe");
 
   subtestDone();
 }
 
 waitUntilApzStable().then(runContinuation(test));
 
 </script>
--- a/gfx/layers/apz/test/mochitest/helper_hittest_clippath.html
+++ b/gfx/layers/apz/test/mochitest/helper_hittest_clippath.html
@@ -39,65 +39,77 @@ function* test(testDriver) {
   var subwindow = document.getElementById("sub").contentWindow;
   var subscroller = subwindow.document.scrollingElement;
   var subutils = SpecialPowers.getDOMWindowUtils(subwindow);
   subutils.setDisplayPortForElement(0, 0, 400, 1000, subscroller, 1);
   yield waitForApzFlushedRepaints(testDriver);
 
   var rootViewId = utils.getViewId(document.scrollingElement);
   var iframeViewId = subutils.getViewId(subscroller);
+  var layersId = utils.getLayersId();
+  is(subutils.getLayersId(), layersId, "iframe is not OOP");
 
   checkHitResult(hitTest({ x: 10, y: 10 }),
       APZHitResultFlags.VISIBLE,
       iframeViewId,
+      layersId,
       "(simple) uninteresting point inside the iframe");
   checkHitResult(hitTest({ x: 500, y: 10 }),
       APZHitResultFlags.VISIBLE,
       rootViewId,
+      layersId,
       "(simple) uninteresting point in the root scroller");
   checkHitResult(hitTest({ x: 110, y: 110 }),
       APZHitResultFlags.VISIBLE,
       iframeViewId,
+      layersId,
       "(simple) point in the iframe behind overlaying div, but outside the bounding box of the clip path");
   checkHitResult(hitTest({ x: 160, y: 160 }),
       config.isWebRender ? APZHitResultFlags.VISIBLE
                          : APZHitResultFlags.VISIBLE | APZHitResultFlags.IRREGULAR_AREA,
       config.isWebRender ? iframeViewId : rootViewId,
+      layersId,
       "(simple) point in the iframe behind overlaying div, inside the bounding box of the clip path, but outside the actual clip shape");
   checkHitResult(hitTest({ x: 300, y: 200 }),
       config.isWebRender ? APZHitResultFlags.VISIBLE
                          : APZHitResultFlags.VISIBLE | APZHitResultFlags.IRREGULAR_AREA,
       rootViewId,
+      layersId,
       "(simple) point inside the clip shape of the overlaying div");
 
   // Now we turn the "simple" clip-path that WR can handle into a more complex
   // one that needs a mask. Then run the checks again; the expected results for
   // WR are slightly different
   document.getElementById("clipped").style.clipPath = "polygon(50px 200px, 75px 75px, 200px 50px, 350px 200px, 200px 350px)";
   yield waitForApzFlushedRepaints(testDriver);
 
   checkHitResult(hitTest({ x: 10, y: 10 }),
       APZHitResultFlags.VISIBLE,
       iframeViewId,
+      layersId,
       "(complex) uninteresting point inside the iframe");
   checkHitResult(hitTest({ x: 500, y: 10 }),
       APZHitResultFlags.VISIBLE,
       rootViewId,
+      layersId,
       "(complex) uninteresting point in the root scroller");
   checkHitResult(hitTest({ x: 110, y: 110 }),
       APZHitResultFlags.VISIBLE,
       iframeViewId,
+      layersId,
       "(complex) point in the iframe behind overlaying div, but outside the bounding box of the clip path");
   checkHitResult(hitTest({ x: 160, y: 160 }),
       APZHitResultFlags.VISIBLE | APZHitResultFlags.IRREGULAR_AREA,
       rootViewId,
+      layersId,
       "(complex) point in the iframe behind overlaying div, inside the bounding box of the clip path, but outside the actual clip shape");
   checkHitResult(hitTest({ x: 300, y: 200 }),
       APZHitResultFlags.VISIBLE | APZHitResultFlags.IRREGULAR_AREA,
       rootViewId,
+      layersId,
       "(complex) point inside the clip shape of the overlaying div");
 }
 
 waitUntilApzStable()
     .then(runContinuation(test))
     .then(subtestDone);
 </script>
 </body></html>
--- a/gfx/layers/apz/test/mochitest/helper_hittest_clipped_fixed_modal.html
+++ b/gfx/layers/apz/test/mochitest/helper_hittest_clipped_fixed_modal.html
@@ -68,16 +68,17 @@ function* test(testDriver) {
   var subframe = document.querySelector(".content");
   utils.setDisplayPortForElement(0, 0, 800, 2000, subframe, 1);
   yield waitForApzFlushedRepaints(testDriver);
 
   var target = document.querySelector(".content");
   checkHitResult(hitTest(centerOf(target)),
                  APZHitResultFlags.VISIBLE,
                  utils.getViewId(subframe),
+                 utils.getLayersId(),
                  "content covered by a clipped fixed div");
 
   subtestDone();
 }
 
 waitUntilApzStable().then(runContinuation(test));
 
 </script>
--- a/gfx/layers/apz/test/mochitest/helper_hittest_fixed_in_scrolled_transform.html
+++ b/gfx/layers/apz/test/mochitest/helper_hittest_fixed_in_scrolled_transform.html
@@ -71,16 +71,17 @@ function* test(testDriver) {
   var subframe = document.querySelector(".subframe");
   utils.setDisplayPortForElement(0, 0, 800, 2000, subframe, 1);
   yield waitForApzFlushedRepaints(testDriver);
 
   var target = document.querySelector(".absoluteClip");
   checkHitResult(hitTest(centerOf(target)),
                  APZHitResultFlags.VISIBLE,
                  utils.getViewId(subframe),
+                 utils.getLayersId(),
                  "fixed item inside a scrolling transform");
 
   subtestDone();
 }
 
 waitUntilApzStable().then(runContinuation(test));
 
 </script>
--- a/gfx/layers/apz/test/mochitest/helper_hittest_float_bug1434846.html
+++ b/gfx/layers/apz/test/mochitest/helper_hittest_float_bug1434846.html
@@ -36,16 +36,17 @@
 
 function* test(testDriver) {
   var utils = getHitTestConfig().utils;
 
   hitTestScrollbar({
     element: document.getElementById("subframe"),
     directions: { vertical: true },
     expectedScrollId: utils.getViewId(document.scrollingElement),
+    expectedLayersId: utils.getLayersId(),
     trackLocation: ScrollbarTrackLocation.START,
     expectThumb: true,
     layerState: LayerState.INACTIVE,
   });
 
   subtestDone();
 }
 
--- a/gfx/layers/apz/test/mochitest/helper_hittest_float_bug1443518.html
+++ b/gfx/layers/apz/test/mochitest/helper_hittest_float_bug1443518.html
@@ -36,16 +36,17 @@
 
 function* test(testDriver) {
   var utils = getHitTestConfig().utils;
 
   hitTestScrollbar({
     element: document.getElementById("subframe"),
     directions: { horizontal: true },
     expectedScrollId: utils.getViewId(document.scrollingElement),
+    expectedLayersId: utils.getLayersId(),
     trackLocation: ScrollbarTrackLocation.START,
     expectThumb: true,
     layerState: LayerState.INACTIVE,
   });
 
   subtestDone();
 }
 
--- a/gfx/layers/apz/test/mochitest/helper_hittest_nested_transforms_bug1459696.html
+++ b/gfx/layers/apz/test/mochitest/helper_hittest_nested_transforms_bug1459696.html
@@ -63,16 +63,17 @@ function* test(testDriver) {
   var utils = getHitTestConfig().utils;
 
   var leftPane = document.getElementById("left-pane");
 
   checkHitResult(
     hitTest(centerOf(leftPane)),
     APZHitResultFlags.VISIBLE,
     utils.getViewId(leftPane),
+    utils.getLayersId(),
     "left pane was successfully hit");
 
   subtestDone();
 }
 
 waitUntilApzStable().then(runContinuation(test));
 
 </script>
--- a/gfx/layers/apz/test/mochitest/helper_hittest_pointerevents_svg.html
+++ b/gfx/layers/apz/test/mochitest/helper_hittest_pointerevents_svg.html
@@ -96,71 +96,81 @@ function* test(testDriver) {
 
   var rootViewId = utils.getViewId(document.scrollingElement);
   for (var testId = 1; testId <= 4; testId++) {
     var target = document.querySelector(`#testcase${testId} .scroller`);
     var scrollerViewId = utils.getViewId(target);
     checkHitResult(hitTest(centerOf(target)),
                    APZHitResultFlags.VISIBLE,
                    scrollerViewId,
+                   utils.getLayersId(),
                    `center of scroller in testcase ${testId}`);
 
     var bounds = target.getBoundingClientRect();
     var verticalScrollbarWidth = bounds.width - target.clientWidth;
     var horizontalScrollbarHeight = bounds.height - target.clientHeight;
 
     // these points should all hit the target scroller
     checkHitResult(hitTest({x: bounds.x + 1, y: bounds.y + 1}),
                    APZHitResultFlags.VISIBLE,
                    scrollerViewId,
+                   utils.getLayersId(),
                    `top left of scroller in testcase ${testId}`);
     checkHitResult(hitTest({x: bounds.x + 1, y: bounds.y + (bounds.height / 2)}),
                    APZHitResultFlags.VISIBLE,
                    scrollerViewId,
+                   utils.getLayersId(),
                    `middle left of scroller in testcase ${testId}`);
     checkHitResult(hitTest({x: bounds.x + 1, y: bounds.bottom - horizontalScrollbarHeight - 1}),
                    APZHitResultFlags.VISIBLE,
                    scrollerViewId,
+                   utils.getLayersId(),
                    `bottom left (excluding scrollbar) of scroller in testcase ${testId}`);
     if (horizontalScrollbarHeight > 0) {
       checkHitResult(hitTest({x: bounds.x + 1, y: bounds.bottom - 1}),
                      APZHitResultFlags.VISIBLE | APZHitResultFlags.SCROLLBAR,
                      scrollerViewId,
+                     utils.getLayersId(),
                      `bottom left of scroller in testcase ${testId}`);
     }
 
     // With the first two cases (circle masks) both WebRender and non-WebRender
     // emit dispatch-to-content regions for the right side, so for now we just
     // test for that. Eventually WebRender should be able to stop emitting DTC
     // and we can update this test to be more precise in that case.
     // For the two rectangular test cases we get precise results rather than
     // dispatch-to-content.
     if (testId == 1 || testId == 2) {
       checkHitResult(hitTest({x: bounds.right - verticalScrollbarWidth - 1, y: bounds.y + 1}),
                      APZHitResultFlags.VISIBLE | APZHitResultFlags.IRREGULAR_AREA,
                      rootViewId,
+                     utils.getLayersId(),
                      `top right of scroller in testcase ${testId}`);
       checkHitResult(hitTest({x: bounds.right - verticalScrollbarWidth - 1, y: bounds.bottom - horizontalScrollbarHeight - 1}),
                      APZHitResultFlags.VISIBLE | APZHitResultFlags.IRREGULAR_AREA,
                      rootViewId,
+                     utils.getLayersId(),
                      `bottom right of scroller in testcase ${testId}`);
     } else {
       checkHitResult(hitTest({x: bounds.right - verticalScrollbarWidth - 1, y: bounds.y + 1}),
                      APZHitResultFlags.VISIBLE,
                      scrollerViewId,
+                     utils.getLayersId(),
                      `top right of scroller in testcase ${testId}`);
       checkHitResult(hitTest({x: bounds.right - verticalScrollbarWidth - 1, y: bounds.bottom - horizontalScrollbarHeight - 1}),
                      APZHitResultFlags.VISIBLE,
                      scrollerViewId,
+                     utils.getLayersId(),
                      `bottom right of scroller in testcase ${testId}`);
     }
 
     checkHitResult(hitTest({x: bounds.right - 1, y: bounds.y + (bounds.height / 2)}),
                    APZHitResultFlags.VISIBLE | APZHitResultFlags.IRREGULAR_AREA,
                    rootViewId,
+                   utils.getLayersId(),
                    `middle right of scroller in testcase ${testId}`);
   }
 }
 
 waitUntilApzStable()
     .then(runContinuation(test))
     .then(subtestDone);
 </script>
--- a/gfx/layers/apz/test/mochitest/helper_hittest_sticky_bug1478304.html
+++ b/gfx/layers/apz/test/mochitest/helper_hittest_sticky_bug1478304.html
@@ -41,16 +41,17 @@ function* test(testDriver) {
 
   var subframe = document.getElementById("subframe");
   var sticky = document.getElementById("sticky");
 
   checkHitResult(
     hitTest(centerOf(sticky)),
     APZHitResultFlags.VISIBLE,
     utils.getViewId(subframe),
+    utils.getLayersId(),
     "subframe was successfully hit");
 
   subtestDone();
 }
 
 waitUntilApzStable().then(runContinuation(test));
 
 </script>
--- a/gfx/layers/apz/test/mochitest/helper_hittest_touchaction.html
+++ b/gfx/layers/apz/test/mochitest/helper_hittest_touchaction.html
@@ -108,204 +108,227 @@ var config = getHitTestConfig();
 function* test(testDriver) {
   for (var scroller of document.querySelectorAll(".taBigBox > div")) {
     // layerize all the scrollable divs
     config.utils.setDisplayPortForElement(0, 0, 100, 100, scroller, 1);
   }
   yield waitForApzFlushedRepaints(testDriver);
 
   var scrollId = config.utils.getViewId(document.scrollingElement);
+  var layersId = config.utils.getLayersId();
 
   // Elements with APZ aware listeners round-trip through the dispatch-to-content
   // region and end up as IRREGULAR_AREA when WebRender is disabled.
   var touchListenerFlag = config.isWebRender
         ? APZHitResultFlags.APZ_AWARE_LISTENERS
         : APZHitResultFlags.IRREGULAR_AREA;
 
   checkHitResult(
       hitTest(centerOf("taNone")),
       APZHitResultFlags.VISIBLE |
       APZHitResultFlags.PAN_X_DISABLED |
       APZHitResultFlags.PAN_Y_DISABLED |
       APZHitResultFlags.PINCH_ZOOM_DISABLED |
       APZHitResultFlags.DOUBLE_TAP_ZOOM_DISABLED,
       scrollId,
+      layersId,
       "touch-action: none");
   checkHitResult(
       hitTest(centerOf("taInnerNonePanX")),
       APZHitResultFlags.VISIBLE |
       APZHitResultFlags.PAN_X_DISABLED |
       APZHitResultFlags.PAN_Y_DISABLED |
       APZHitResultFlags.PINCH_ZOOM_DISABLED |
       APZHitResultFlags.DOUBLE_TAP_ZOOM_DISABLED,
       scrollId,
+      layersId,
       "touch-action: pan-x inside touch-action: none");
   checkHitResult(
       hitTest(centerOf("taInnerNoneManip")),
       APZHitResultFlags.VISIBLE |
       APZHitResultFlags.PAN_X_DISABLED |
       APZHitResultFlags.PAN_Y_DISABLED |
       APZHitResultFlags.PINCH_ZOOM_DISABLED |
       APZHitResultFlags.DOUBLE_TAP_ZOOM_DISABLED,
       scrollId,
+      layersId,
       "touch-action: manipulation inside touch-action: none");
 
   checkHitResult(
       hitTest(centerOf("taPanX")),
       APZHitResultFlags.VISIBLE |
       APZHitResultFlags.PAN_Y_DISABLED |
       APZHitResultFlags.PINCH_ZOOM_DISABLED |
       APZHitResultFlags.DOUBLE_TAP_ZOOM_DISABLED,
       scrollId,
+      layersId,
       "touch-action: pan-x");
   checkHitResult(
       hitTest(centerOf("taInnerPanXY")),
       APZHitResultFlags.VISIBLE |
       APZHitResultFlags.PAN_X_DISABLED |
       APZHitResultFlags.PAN_Y_DISABLED |
       APZHitResultFlags.PINCH_ZOOM_DISABLED |
       APZHitResultFlags.DOUBLE_TAP_ZOOM_DISABLED,
       scrollId,
+      layersId,
       "touch-action: pan-y inside touch-action: pan-x");
   checkHitResult(
       hitTest(centerOf("taInnerPanXManip")),
       APZHitResultFlags.VISIBLE |
       APZHitResultFlags.PAN_Y_DISABLED |
       APZHitResultFlags.PINCH_ZOOM_DISABLED |
       APZHitResultFlags.DOUBLE_TAP_ZOOM_DISABLED,
       scrollId,
+      layersId,
       "touch-action: manipulation inside touch-action: pan-x");
 
   checkHitResult(
       hitTest(centerOf("taPanY")),
       APZHitResultFlags.VISIBLE |
       APZHitResultFlags.PAN_X_DISABLED |
       APZHitResultFlags.PINCH_ZOOM_DISABLED |
       APZHitResultFlags.DOUBLE_TAP_ZOOM_DISABLED,
       scrollId,
+      layersId,
       "touch-action: pan-y");
   checkHitResult(
       hitTest(centerOf("taInnerPanYX")),
       APZHitResultFlags.VISIBLE |
       APZHitResultFlags.PAN_X_DISABLED |
       APZHitResultFlags.PAN_Y_DISABLED |
       APZHitResultFlags.PINCH_ZOOM_DISABLED |
       APZHitResultFlags.DOUBLE_TAP_ZOOM_DISABLED,
       scrollId,
+      layersId,
       "touch-action: pan-x inside touch-action: pan-y");
   checkHitResult(
       hitTest(centerOf("taInnerPanYY")),
       APZHitResultFlags.VISIBLE |
       APZHitResultFlags.PAN_X_DISABLED |
       APZHitResultFlags.PINCH_ZOOM_DISABLED |
       APZHitResultFlags.DOUBLE_TAP_ZOOM_DISABLED,
       scrollId,
+      layersId,
       "touch-action: pan-y inside touch-action: pan-y");
 
   checkHitResult(
       hitTest(centerOf("taPanXY")),
       APZHitResultFlags.VISIBLE |
       APZHitResultFlags.PINCH_ZOOM_DISABLED |
       APZHitResultFlags.DOUBLE_TAP_ZOOM_DISABLED,
       scrollId,
+      layersId,
       "touch-action: pan-x pan-y");
   checkHitResult(
       hitTest(centerOf("taInnerPanXYNone")),
       APZHitResultFlags.VISIBLE |
       APZHitResultFlags.PAN_X_DISABLED |
       APZHitResultFlags.PAN_Y_DISABLED |
       APZHitResultFlags.PINCH_ZOOM_DISABLED |
       APZHitResultFlags.DOUBLE_TAP_ZOOM_DISABLED,
       scrollId,
+      layersId,
       "touch-action: none inside touch-action: pan-x pan-y");
 
   checkHitResult(
       hitTest(centerOf("taManip")),
       APZHitResultFlags.VISIBLE |
       APZHitResultFlags.DOUBLE_TAP_ZOOM_DISABLED,
       scrollId,
+      layersId,
       "touch-action: manipulation");
   checkHitResult(
       hitTest(centerOf("taInnerManipPanX")),
       APZHitResultFlags.VISIBLE |
       APZHitResultFlags.PAN_Y_DISABLED |
       APZHitResultFlags.PINCH_ZOOM_DISABLED |
       APZHitResultFlags.DOUBLE_TAP_ZOOM_DISABLED,
       scrollId,
+      layersId,
       "touch-action: pan-x inside touch-action: manipulation");
   checkHitResult(
       hitTest(centerOf("taInnerManipNone")),
       APZHitResultFlags.VISIBLE |
       APZHitResultFlags.PAN_X_DISABLED |
       APZHitResultFlags.PAN_Y_DISABLED |
       APZHitResultFlags.PINCH_ZOOM_DISABLED |
       APZHitResultFlags.DOUBLE_TAP_ZOOM_DISABLED,
       scrollId,
+      layersId,
       "touch-action: none inside touch-action: manipulation");
   checkHitResult(
       hitTest(centerOf("taInnerManipListener")),
       APZHitResultFlags.VISIBLE |
       touchListenerFlag |
       APZHitResultFlags.DOUBLE_TAP_ZOOM_DISABLED,
       scrollId,
+      layersId,
       "div with touch listener inside touch-action: manipulation");
 
   checkHitResult(
       hitTest(centerOf("taListener")),
       APZHitResultFlags.VISIBLE |
       touchListenerFlag,
       scrollId,
+      layersId,
       "div with touch listener");
   checkHitResult(
       hitTest(centerOf("taInnerListenerPanX")),
       APZHitResultFlags.VISIBLE |
       touchListenerFlag |
       APZHitResultFlags.PAN_Y_DISABLED |
       APZHitResultFlags.PINCH_ZOOM_DISABLED |
       APZHitResultFlags.DOUBLE_TAP_ZOOM_DISABLED,
       scrollId,
+      layersId,
       "touch-action: pan-x inside div with touch listener");
 
   checkHitResult(
       hitTest(centerOf("taScrollerPanY")),
       APZHitResultFlags.VISIBLE |
       APZHitResultFlags.PAN_X_DISABLED |
       APZHitResultFlags.PINCH_ZOOM_DISABLED |
       APZHitResultFlags.DOUBLE_TAP_ZOOM_DISABLED,
       config.utils.getViewId(document.getElementById("taScrollerPanY")),
+      layersId,
       "touch-action: pan-y on scroller");
   checkHitResult(
       hitTest(centerOf("taScroller")),
       APZHitResultFlags.VISIBLE |
       APZHitResultFlags.PAN_X_DISABLED |
       APZHitResultFlags.PINCH_ZOOM_DISABLED |
       APZHitResultFlags.DOUBLE_TAP_ZOOM_DISABLED,
       config.utils.getViewId(document.getElementById("taScroller")),
+      layersId,
       "touch-action: pan-y on div inside scroller");
   checkHitResult(
       hitTest(centerOf("taScroller2")),
       APZHitResultFlags.VISIBLE |
       APZHitResultFlags.PINCH_ZOOM_DISABLED |
       APZHitResultFlags.DOUBLE_TAP_ZOOM_DISABLED,
       config.utils.getViewId(document.getElementById("taScroller2")),
+      layersId,
       "zooming restrictions from pan-x outside scroller get inherited in");
 
   checkHitResult(
       hitTest(centerOf("taScrollerPanX")),
       APZHitResultFlags.VISIBLE |
       APZHitResultFlags.PAN_Y_DISABLED |
       APZHitResultFlags.PINCH_ZOOM_DISABLED |
       APZHitResultFlags.DOUBLE_TAP_ZOOM_DISABLED,
       config.utils.getViewId(document.getElementById("taScrollerPanX")),
+      layersId,
       "touch-action: pan-x on scroller inside manipulation");
   checkHitResult(
       hitTest(centerOf("taScroller3")),
       APZHitResultFlags.VISIBLE |
       APZHitResultFlags.DOUBLE_TAP_ZOOM_DISABLED,
       config.utils.getViewId(document.getElementById("taScroller3")),
+      layersId,
       "touch-action: manipulation outside scroller gets inherited in");
 }
 
 if (!config.isWebRender) {
   ok(true, "This test is WebRender-only because we get a bunch of dispatch-to-content regions without it and the test isn't very interesting.");
   subtestDone();
 } else {
   waitUntilApzStable()
--- a/gfx/layers/apz/testutil/APZTestData.cpp
+++ b/gfx/layers/apz/testutil/APZTestData.cpp
@@ -82,16 +82,17 @@ struct APZTestDataToJSConverter {
     aOutHitResult.mScreenX.Construct() = aResult.point.x;
     aOutHitResult.mScreenY.Construct() = aResult.point.y;
     static_assert(MaxEnumValue<gfx::CompositorHitTestInfo::valueType>::value <
                       std::numeric_limits<uint16_t>::digits,
                   "CompositorHitTestFlags MAX value have to be less than "
                   "number of bits in uint16_t");
     aOutHitResult.mHitResult.Construct() =
         static_cast<uint16_t>(aResult.result.serialize());
+    aOutHitResult.mLayersId.Construct() = aResult.layersId.mId;
     aOutHitResult.mScrollId.Construct() = aResult.scrollId;
   }
 };
 
 bool APZTestData::ToJS(JS::MutableHandleValue aOutValue,
                        JSContext* aContext) const {
   dom::APZTestData result;
   APZTestDataToJSConverter::ConvertAPZTestData(*this, result);
--- a/gfx/layers/apz/testutil/APZTestData.h
+++ b/gfx/layers/apz/testutil/APZTestData.h
@@ -70,18 +70,19 @@ class APZTestData {
   }
   void LogTestDataForRepaintRequest(SequenceNumber aSequenceNumber,
                                     ViewID aScrollId, const std::string& aKey,
                                     const std::string& aValue) {
     LogTestDataImpl(mRepaintRequests, aSequenceNumber, aScrollId, aKey, aValue);
   }
   void RecordHitResult(const ScreenPoint& aPoint,
                        const mozilla::gfx::CompositorHitTestInfo& aResult,
+                       const LayersId& aLayersId,
                        const ViewID& aScrollId) {
-    mHitResults.AppendElement(HitResult{aPoint, aResult, aScrollId});
+    mHitResults.AppendElement(HitResult{aPoint, aResult, aLayersId, aScrollId});
   }
   void RecordAdditionalData(const std::string& aKey,
                             const std::string& aValue) {
     mAdditionalData[aKey] = aValue;
   }
 
   // Convert this object to a JS representation.
   bool ToJS(JS::MutableHandleValue aOutValue, JSContext* aContext) const;
@@ -92,16 +93,17 @@ class APZTestData {
   struct ScrollFrameData : ScrollFrameDataBase {};
   typedef std::map<ViewID, ScrollFrameData> BucketBase;
   struct Bucket : BucketBase {};
   typedef std::map<SequenceNumber, Bucket> DataStoreBase;
   struct DataStore : DataStoreBase {};
   struct HitResult {
     ScreenPoint point;
     mozilla::gfx::CompositorHitTestInfo result;
+    LayersId layersId;
     ViewID scrollId;
   };
 
  private:
   DataStore mPaints;
   DataStore mRepaintRequests;
   nsTArray<HitResult> mHitResults;
   // Additional free-form data that's not grouped paint or scroll frame.
@@ -192,22 +194,24 @@ struct ParamTraits<mozilla::layers::APZT
 
 template <>
 struct ParamTraits<mozilla::layers::APZTestData::HitResult> {
   typedef mozilla::layers::APZTestData::HitResult paramType;
 
   static void Write(Message* aMsg, const paramType& aParam) {
     WriteParam(aMsg, aParam.point);
     WriteParam(aMsg, aParam.result);
+    WriteParam(aMsg, aParam.layersId);
     WriteParam(aMsg, aParam.scrollId);
   }
 
   static bool Read(const Message* aMsg, PickleIterator* aIter,
                    paramType* aResult) {
     return (ReadParam(aMsg, aIter, &aResult->point) &&
             ReadParam(aMsg, aIter, &aResult->result) &&
+            ReadParam(aMsg, aIter, &aResult->layersId) &&
             ReadParam(aMsg, aIter, &aResult->scrollId));
   }
 };
 
 }  // namespace IPC
 
 #endif /* mozilla_layers_APZTestData_h */