summaryrefslogtreecommitdiffstats
path: root/gfx/layers/apz/test/mochitest
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /gfx/layers/apz/test/mochitest
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'gfx/layers/apz/test/mochitest')
-rw-r--r--gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js261
-rw-r--r--gfx/layers/apz/test/mochitest/apz_test_utils.js403
-rw-r--r--gfx/layers/apz/test/mochitest/chrome.ini9
-rw-r--r--gfx/layers/apz/test/mochitest/helper_basic_pan.html38
-rw-r--r--gfx/layers/apz/test/mochitest/helper_bug1151663.html83
-rw-r--r--gfx/layers/apz/test/mochitest/helper_bug1162771.html104
-rw-r--r--gfx/layers/apz/test/mochitest/helper_bug1271432.html574
-rw-r--r--gfx/layers/apz/test/mochitest/helper_bug1280013.html74
-rw-r--r--gfx/layers/apz/test/mochitest/helper_bug1285070.html44
-rw-r--r--gfx/layers/apz/test/mochitest/helper_bug1299195.html47
-rw-r--r--gfx/layers/apz/test/mochitest/helper_bug982141.html149
-rw-r--r--gfx/layers/apz/test/mochitest/helper_click.html41
-rw-r--r--gfx/layers/apz/test/mochitest/helper_div_pan.html44
-rw-r--r--gfx/layers/apz/test/mochitest/helper_drag_click.html43
-rw-r--r--gfx/layers/apz/test/mochitest/helper_drag_scroll.html603
-rw-r--r--gfx/layers/apz/test/mochitest/helper_iframe1.html14
-rw-r--r--gfx/layers/apz/test/mochitest/helper_iframe2.html14
-rw-r--r--gfx/layers/apz/test/mochitest/helper_iframe_pan.html41
-rw-r--r--gfx/layers/apz/test/mochitest/helper_long_tap.html81
-rw-r--r--gfx/layers/apz/test/mochitest/helper_scroll_inactive_perspective.html46
-rw-r--r--gfx/layers/apz/test/mochitest/helper_scroll_inactive_zindex.html47
-rw-r--r--gfx/layers/apz/test/mochitest/helper_scroll_on_position_fixed.html62
-rw-r--r--gfx/layers/apz/test/mochitest/helper_scrollto_tap.html61
-rw-r--r--gfx/layers/apz/test/mochitest/helper_subframe_style.css15
-rw-r--r--gfx/layers/apz/test/mochitest/helper_tall.html504
-rw-r--r--gfx/layers/apz/test/mochitest/helper_tap.html32
-rw-r--r--gfx/layers/apz/test/mochitest/helper_tap_fullzoom.html33
-rw-r--r--gfx/layers/apz/test/mochitest/helper_tap_passive.html64
-rw-r--r--gfx/layers/apz/test/mochitest/helper_touch_action.html115
-rw-r--r--gfx/layers/apz/test/mochitest/helper_touch_action_complex.html143
-rw-r--r--gfx/layers/apz/test/mochitest/helper_touch_action_regions.html246
-rw-r--r--gfx/layers/apz/test/mochitest/mochitest.ini67
-rw-r--r--gfx/layers/apz/test/mochitest/test_bug1151663.html38
-rw-r--r--gfx/layers/apz/test/mochitest/test_bug1151667.html65
-rw-r--r--gfx/layers/apz/test/mochitest/test_bug1253683.html59
-rw-r--r--gfx/layers/apz/test/mochitest/test_bug1277814.html106
-rw-r--r--gfx/layers/apz/test/mochitest/test_bug1304689-2.html131
-rw-r--r--gfx/layers/apz/test/mochitest/test_bug1304689.html135
-rw-r--r--gfx/layers/apz/test/mochitest/test_bug982141.html38
-rw-r--r--gfx/layers/apz/test/mochitest/test_frame_reconstruction.html218
-rw-r--r--gfx/layers/apz/test/mochitest/test_group_mouseevents.html35
-rw-r--r--gfx/layers/apz/test/mochitest/test_group_pointerevents.html31
-rw-r--r--gfx/layers/apz/test/mochitest/test_group_touchevents.html104
-rw-r--r--gfx/layers/apz/test/mochitest/test_group_wheelevents.html41
-rw-r--r--gfx/layers/apz/test/mochitest/test_group_zoom.html44
-rw-r--r--gfx/layers/apz/test/mochitest/test_interrupted_reflow.html719
-rw-r--r--gfx/layers/apz/test/mochitest/test_layerization.html214
-rw-r--r--gfx/layers/apz/test/mochitest/test_scroll_inactive_bug1190112.html541
-rw-r--r--gfx/layers/apz/test/mochitest/test_scroll_inactive_flattened_frame.html49
-rw-r--r--gfx/layers/apz/test/mochitest/test_scroll_subframe_scrollbar.html117
-rw-r--r--gfx/layers/apz/test/mochitest/test_smoothness.html77
-rw-r--r--gfx/layers/apz/test/mochitest/test_touch_listeners_impacting_wheel.html114
-rw-r--r--gfx/layers/apz/test/mochitest/test_wheel_scroll.html106
-rw-r--r--gfx/layers/apz/test/mochitest/test_wheel_transactions.html137
54 files changed, 7221 insertions, 0 deletions
diff --git a/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js b/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js
new file mode 100644
index 000000000..7f820a936
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js
@@ -0,0 +1,261 @@
+// Utilities for synthesizing of native events.
+
+function getPlatform() {
+ if (navigator.platform.indexOf("Win") == 0) {
+ return "windows";
+ }
+ if (navigator.platform.indexOf("Mac") == 0) {
+ return "mac";
+ }
+ // Check for Android before Linux
+ if (navigator.appVersion.indexOf("Android") >= 0) {
+ return "android"
+ }
+ if (navigator.platform.indexOf("Linux") == 0) {
+ return "linux";
+ }
+ return "unknown";
+}
+
+function nativeVerticalWheelEventMsg() {
+ switch (getPlatform()) {
+ case "windows": return 0x020A; // WM_MOUSEWHEEL
+ case "mac": return 0; // value is unused, can be anything
+ case "linux": return 4; // value is unused, pass GDK_SCROLL_SMOOTH anyway
+ }
+ throw "Native wheel events not supported on platform " + getPlatform();
+}
+
+function nativeHorizontalWheelEventMsg() {
+ switch (getPlatform()) {
+ case "windows": return 0x020E; // WM_MOUSEHWHEEL
+ case "mac": return 0; // value is unused, can be anything
+ case "linux": return 4; // value is unused, pass GDK_SCROLL_SMOOTH anyway
+ }
+ throw "Native wheel events not supported on platform " + getPlatform();
+}
+
+// Given a pixel scrolling delta, converts it to the platform's native units.
+function nativeScrollUnits(aElement, aDimen) {
+ switch (getPlatform()) {
+ case "linux": {
+ // GTK deltas are treated as line height divided by 3 by gecko.
+ var targetWindow = aElement.ownerDocument.defaultView;
+ var lineHeight = targetWindow.getComputedStyle(aElement)["font-size"];
+ return aDimen / (parseInt(lineHeight) * 3);
+ }
+ }
+ return aDimen;
+}
+
+function nativeMouseDownEventMsg() {
+ switch (getPlatform()) {
+ case "windows": return 2; // MOUSEEVENTF_LEFTDOWN
+ case "mac": return 1; // NSLeftMouseDown
+ case "linux": return 4; // GDK_BUTTON_PRESS
+ case "android": return 5; // ACTION_POINTER_DOWN
+ }
+ throw "Native mouse-down events not supported on platform " + getPlatform();
+}
+
+function nativeMouseMoveEventMsg() {
+ switch (getPlatform()) {
+ case "windows": return 1; // MOUSEEVENTF_MOVE
+ case "mac": return 5; // NSMouseMoved
+ case "linux": return 3; // GDK_MOTION_NOTIFY
+ case "android": return 7; // ACTION_HOVER_MOVE
+ }
+ throw "Native mouse-move events not supported on platform " + getPlatform();
+}
+
+function nativeMouseUpEventMsg() {
+ switch (getPlatform()) {
+ case "windows": return 4; // MOUSEEVENTF_LEFTUP
+ case "mac": return 2; // NSLeftMouseUp
+ case "linux": return 7; // GDK_BUTTON_RELEASE
+ case "android": return 6; // ACTION_POINTER_UP
+ }
+ throw "Native mouse-up events not supported on platform " + getPlatform();
+}
+
+// Convert (aX, aY), in CSS pixels relative to aElement's bounding rect,
+// to device pixels relative to the screen.
+function coordinatesRelativeToScreen(aX, aY, aElement) {
+ var targetWindow = aElement.ownerDocument.defaultView;
+ var scale = targetWindow.devicePixelRatio;
+ var rect = aElement.getBoundingClientRect();
+ return {
+ x: (targetWindow.mozInnerScreenX + rect.left + aX) * scale,
+ y: (targetWindow.mozInnerScreenY + rect.top + aY) * scale
+ };
+}
+
+// Get the bounding box of aElement, and return it in device pixels
+// relative to the screen.
+function rectRelativeToScreen(aElement) {
+ var targetWindow = aElement.ownerDocument.defaultView;
+ var scale = targetWindow.devicePixelRatio;
+ var rect = aElement.getBoundingClientRect();
+ return {
+ x: (targetWindow.mozInnerScreenX + rect.left) * scale,
+ y: (targetWindow.mozInnerScreenY + rect.top) * scale,
+ w: (rect.width * scale),
+ h: (rect.height * scale)
+ };
+}
+
+// Synthesizes a native mousewheel event and returns immediately. This does not
+// guarantee anything; you probably want to use one of the other functions below
+// which actually wait for results.
+// aX and aY are relative to the top-left of |aElement|'s containing window.
+// aDeltaX and aDeltaY are pixel deltas, and aObserver can be left undefined
+// if not needed.
+function synthesizeNativeWheel(aElement, aX, aY, aDeltaX, aDeltaY, aObserver) {
+ var pt = coordinatesRelativeToScreen(aX, aY, aElement);
+ if (aDeltaX && aDeltaY) {
+ throw "Simultaneous wheeling of horizontal and vertical is not supported on all platforms.";
+ }
+ aDeltaX = nativeScrollUnits(aElement, aDeltaX);
+ aDeltaY = nativeScrollUnits(aElement, aDeltaY);
+ var msg = aDeltaX ? nativeHorizontalWheelEventMsg() : nativeVerticalWheelEventMsg();
+ var utils = SpecialPowers.getDOMWindowUtils(aElement.ownerDocument.defaultView);
+ utils.sendNativeMouseScrollEvent(pt.x, pt.y, msg, aDeltaX, aDeltaY, 0, 0, 0, aElement, aObserver);
+ return true;
+}
+
+// Synthesizes a native mousewheel event and invokes the callback once the
+// request has been successfully made to the OS. This does not necessarily
+// guarantee that the OS generates the event we requested. See
+// synthesizeNativeWheel for details on the parameters.
+function synthesizeNativeWheelAndWaitForObserver(aElement, aX, aY, aDeltaX, aDeltaY, aCallback) {
+ var observer = {
+ observe: function(aSubject, aTopic, aData) {
+ if (aCallback && aTopic == "mousescrollevent") {
+ setTimeout(aCallback, 0);
+ }
+ }
+ };
+ return synthesizeNativeWheel(aElement, aX, aY, aDeltaX, aDeltaY, observer);
+}
+
+// Synthesizes a native mousewheel event and invokes the callback once the
+// wheel event is dispatched to |aElement|'s containing window. If the event
+// targets content in a subdocument, |aElement| should be inside the
+// subdocument. See synthesizeNativeWheel for details on the other parameters.
+function synthesizeNativeWheelAndWaitForWheelEvent(aElement, aX, aY, aDeltaX, aDeltaY, aCallback) {
+ var targetWindow = aElement.ownerDocument.defaultView;
+ targetWindow.addEventListener("wheel", function wheelWaiter(e) {
+ targetWindow.removeEventListener("wheel", wheelWaiter);
+ setTimeout(aCallback, 0);
+ });
+ return synthesizeNativeWheel(aElement, aX, aY, aDeltaX, aDeltaY);
+}
+
+// Synthesizes a native mousewheel event and invokes the callback once the
+// first resulting scroll event is dispatched to |aElement|'s containing window.
+// If the event targets content in a subdocument, |aElement| should be inside
+// the subdocument. See synthesizeNativeWheel for details on the other
+// parameters.
+function synthesizeNativeWheelAndWaitForScrollEvent(aElement, aX, aY, aDeltaX, aDeltaY, aCallback) {
+ var targetWindow = aElement.ownerDocument.defaultView;
+ var useCapture = true; // scroll events don't always bubble
+ targetWindow.addEventListener("scroll", function scrollWaiter(e) {
+ targetWindow.removeEventListener("scroll", scrollWaiter, useCapture);
+ setTimeout(aCallback, 0);
+ }, useCapture);
+ return synthesizeNativeWheel(aElement, aX, aY, aDeltaX, aDeltaY);
+}
+
+// Synthesizes a native mouse move event and returns immediately.
+// aX and aY are relative to the top-left of |aElement|'s containing window.
+function synthesizeNativeMouseMove(aElement, aX, aY) {
+ var pt = coordinatesRelativeToScreen(aX, aY, aElement);
+ var utils = SpecialPowers.getDOMWindowUtils(aElement.ownerDocument.defaultView);
+ utils.sendNativeMouseEvent(pt.x, pt.y, nativeMouseMoveEventMsg(), 0, aElement);
+ return true;
+}
+
+// Synthesizes a native mouse move event and invokes the callback once the
+// mouse move event is dispatched to |aElement|'s containing window. If the event
+// targets content in a subdocument, |aElement| should be inside the
+// subdocument. See synthesizeNativeMouseMove for details on the other
+// parameters.
+function synthesizeNativeMouseMoveAndWaitForMoveEvent(aElement, aX, aY, aCallback) {
+ var targetWindow = aElement.ownerDocument.defaultView;
+ targetWindow.addEventListener("mousemove", function mousemoveWaiter(e) {
+ targetWindow.removeEventListener("mousemove", mousemoveWaiter);
+ setTimeout(aCallback, 0);
+ });
+ return synthesizeNativeMouseMove(aElement, aX, aY);
+}
+
+// Synthesizes a native touch event and dispatches it. aX and aY in CSS pixels
+// relative to the top-left of |aElement|'s bounding rect.
+function synthesizeNativeTouch(aElement, aX, aY, aType, aObserver = null, aTouchId = 0) {
+ var pt = coordinatesRelativeToScreen(aX, aY, aElement);
+ var utils = SpecialPowers.getDOMWindowUtils(aElement.ownerDocument.defaultView);
+ utils.sendNativeTouchPoint(aTouchId, aType, pt.x, pt.y, 1, 90, aObserver);
+ return true;
+}
+
+// A handy constant when synthesizing native touch drag events with the pref
+// "apz.touch_start_tolerance" set to 0. In this case, the first touchmove with
+// a nonzero pixel movement is consumed by the APZ to transition from the
+// "touching" state to the "panning" state, so calls to synthesizeNativeTouchDrag
+// should add an extra pixel pixel for this purpose. The TOUCH_SLOP provides
+// a constant that can be used for this purpose. Note that if the touch start
+// tolerance is set to something higher, the touch slop amount used must be
+// correspondingly increased so as to be higher than the tolerance.
+const TOUCH_SLOP = 1;
+function synthesizeNativeTouchDrag(aElement, aX, aY, aDeltaX, aDeltaY, aObserver = null, aTouchId = 0) {
+ synthesizeNativeTouch(aElement, aX, aY, SpecialPowers.DOMWindowUtils.TOUCH_CONTACT, null, aTouchId);
+ var steps = Math.max(Math.abs(aDeltaX), Math.abs(aDeltaY));
+ for (var i = 1; i < steps; i++) {
+ var dx = i * (aDeltaX / steps);
+ var dy = i * (aDeltaY / steps);
+ synthesizeNativeTouch(aElement, aX + dx, aY + dy, SpecialPowers.DOMWindowUtils.TOUCH_CONTACT, null, aTouchId);
+ }
+ synthesizeNativeTouch(aElement, aX + aDeltaX, aY + aDeltaY, SpecialPowers.DOMWindowUtils.TOUCH_CONTACT, null, aTouchId);
+ return synthesizeNativeTouch(aElement, aX + aDeltaX, aY + aDeltaY, SpecialPowers.DOMWindowUtils.TOUCH_REMOVE, aObserver, aTouchId);
+}
+
+function synthesizeNativeTap(aElement, aX, aY, aObserver = null) {
+ var pt = coordinatesRelativeToScreen(aX, aY, aElement);
+ var utils = SpecialPowers.getDOMWindowUtils(aElement.ownerDocument.defaultView);
+ utils.sendNativeTouchTap(pt.x, pt.y, false, aObserver);
+ return true;
+}
+
+function synthesizeNativeMouseEvent(aElement, aX, aY, aType, aObserver = null) {
+ var pt = coordinatesRelativeToScreen(aX, aY, aElement);
+ var utils = SpecialPowers.getDOMWindowUtils(aElement.ownerDocument.defaultView);
+ utils.sendNativeMouseEvent(pt.x, pt.y, aType, 0, aElement, aObserver);
+ return true;
+}
+
+function synthesizeNativeClick(aElement, aX, aY, aObserver = null) {
+ var pt = coordinatesRelativeToScreen(aX, aY, aElement);
+ var utils = SpecialPowers.getDOMWindowUtils(aElement.ownerDocument.defaultView);
+ utils.sendNativeMouseEvent(pt.x, pt.y, nativeMouseDownEventMsg(), 0, aElement, function() {
+ utils.sendNativeMouseEvent(pt.x, pt.y, nativeMouseUpEventMsg(), 0, aElement, aObserver);
+ });
+ return true;
+}
+
+// Move the mouse to (dx, dy) relative to |element|, and scroll the wheel
+// at that location.
+// Moving the mouse is necessary to avoid wheel events from two consecutive
+// moveMouseAndScrollWheelOver() calls on different elements being incorrectly
+// considered as part of the same wheel transaction.
+// We also wait for the mouse move event to be processed before sending the
+// wheel event, otherwise there is a chance they might get reordered, and
+// we have the transaction problem again.
+function moveMouseAndScrollWheelOver(element, dx, dy, testDriver, waitForScroll = true) {
+ return synthesizeNativeMouseMoveAndWaitForMoveEvent(element, dx, dy, function() {
+ if (waitForScroll) {
+ synthesizeNativeWheelAndWaitForScrollEvent(element, dx, dy, 0, -10, testDriver);
+ } else {
+ synthesizeNativeWheelAndWaitForWheelEvent(element, dx, dy, 0, -10, testDriver);
+ }
+ });
+}
diff --git a/gfx/layers/apz/test/mochitest/apz_test_utils.js b/gfx/layers/apz/test/mochitest/apz_test_utils.js
new file mode 100644
index 000000000..c97738434
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/apz_test_utils.js
@@ -0,0 +1,403 @@
+// Utilities for writing APZ tests using the framework added in bug 961289
+
+// ----------------------------------------------------------------------
+// Functions that convert the APZ test data into a more usable form.
+// Every place we have a WebIDL sequence whose elements are dictionaries
+// with two elements, a key, and a value, we convert this into a JS
+// object with a property for each key/value pair. (This is the structure
+// we really want, but we can't express in directly in WebIDL.)
+// ----------------------------------------------------------------------
+
+function convertEntries(entries) {
+ var result = {};
+ for (var i = 0; i < entries.length; ++i) {
+ result[entries[i].key] = entries[i].value;
+ }
+ return result;
+}
+
+function getPropertyAsRect(scrollFrames, scrollId, prop) {
+ SimpleTest.ok(scrollId in scrollFrames,
+ 'expected scroll frame data for scroll id ' + scrollId);
+ var scrollFrameData = scrollFrames[scrollId];
+ SimpleTest.ok('displayport' in scrollFrameData,
+ 'expected a ' + prop + ' for scroll id ' + scrollId);
+ var value = scrollFrameData[prop];
+ var pieces = value.replace(/[()\s]+/g, '').split(',');
+ SimpleTest.is(pieces.length, 4, "expected string of form (x,y,w,h)");
+ return { x: parseInt(pieces[0]),
+ y: parseInt(pieces[1]),
+ w: parseInt(pieces[2]),
+ h: parseInt(pieces[3]) };
+}
+
+function convertScrollFrameData(scrollFrames) {
+ var result = {};
+ for (var i = 0; i < scrollFrames.length; ++i) {
+ result[scrollFrames[i].scrollId] = convertEntries(scrollFrames[i].entries);
+ }
+ return result;
+}
+
+function convertBuckets(buckets) {
+ var result = {};
+ for (var i = 0; i < buckets.length; ++i) {
+ result[buckets[i].sequenceNumber] = convertScrollFrameData(buckets[i].scrollFrames);
+ }
+ return result;
+}
+
+function convertTestData(testData) {
+ var result = {};
+ result.paints = convertBuckets(testData.paints);
+ result.repaintRequests = convertBuckets(testData.repaintRequests);
+ return result;
+}
+
+// Given APZ test data for a single paint on the compositor side,
+// reconstruct the APZC tree structure from the 'parentScrollId'
+// entries that were logged. More specifically, the subset of the
+// APZC tree structure corresponding to the layer subtree for the
+// content process that triggered the paint, is reconstructed (as
+// the APZ test data only contains information abot this subtree).
+function buildApzcTree(paint) {
+ // The APZC tree can potentially have multiple root nodes,
+ // so we invent a node that is the parent of all roots.
+ // This 'root' does not correspond to an APZC.
+ var root = {scrollId: -1, children: []};
+ for (var scrollId in paint) {
+ paint[scrollId].children = [];
+ paint[scrollId].scrollId = scrollId;
+ }
+ for (var scrollId in paint) {
+ var parentNode = null;
+ if ("hasNoParentWithSameLayersId" in paint[scrollId]) {
+ parentNode = root;
+ } else if ("parentScrollId" in paint[scrollId]) {
+ parentNode = paint[paint[scrollId].parentScrollId];
+ }
+ parentNode.children.push(paint[scrollId]);
+ }
+ return root;
+}
+
+// Given an APZC tree produced by buildApzcTree, return the RCD node in
+// the tree, or null if there was none.
+function findRcdNode(apzcTree) {
+ if (!!apzcTree.isRootContent) { // isRootContent will be undefined or "1"
+ return apzcTree;
+ }
+ for (var i = 0; i < apzcTree.children.length; i++) {
+ var rcd = findRcdNode(apzcTree.children[i]);
+ if (rcd != null) {
+ return rcd;
+ }
+ }
+ return null;
+}
+
+// Return whether an element whose id includes |elementId| has been layerized.
+// Assumes |elementId| will be present in the content description for the
+// element, and not in the content descriptions of other elements.
+function isLayerized(elementId) {
+ var contentTestData = SpecialPowers.getDOMWindowUtils(window).getContentAPZTestData();
+ ok(contentTestData.paints.length > 0, "expected at least one paint");
+ var seqno = contentTestData.paints[contentTestData.paints.length - 1].sequenceNumber;
+ contentTestData = convertTestData(contentTestData);
+ var paint = contentTestData.paints[seqno];
+ for (var scrollId in paint) {
+ if ("contentDescription" in paint[scrollId]) {
+ if (paint[scrollId]["contentDescription"].includes(elementId)) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+function flushApzRepaints(aCallback, aWindow = window) {
+ if (!aCallback) {
+ throw "A callback must be provided!";
+ }
+ var repaintDone = function() {
+ SpecialPowers.Services.obs.removeObserver(repaintDone, "apz-repaints-flushed", false);
+ setTimeout(aCallback, 0);
+ };
+ SpecialPowers.Services.obs.addObserver(repaintDone, "apz-repaints-flushed", false);
+ if (SpecialPowers.getDOMWindowUtils(aWindow).flushApzRepaints()) {
+ dump("Flushed APZ repaints, waiting for callback...\n");
+ } else {
+ dump("Flushing APZ repaints was a no-op, triggering callback directly...\n");
+ repaintDone();
+ }
+}
+
+// Flush repaints, APZ pending repaints, and any repaints resulting from that
+// flush. This is particularly useful if the test needs to reach some sort of
+// "idle" state in terms of repaints. Usually just doing waitForAllPaints
+// followed by flushApzRepaints is sufficient to flush all APZ state back to
+// the main thread, but it can leave a paint scheduled which will get triggered
+// at some later time. For tests that specifically test for painting at
+// specific times, this method is the way to go. Even if in doubt, this is the
+// preferred method as the extra step is "safe" and shouldn't interfere with
+// most tests.
+function waitForApzFlushedRepaints(aCallback) {
+ // First flush the main-thread paints and send transactions to the APZ
+ waitForAllPaints(function() {
+ // Then flush the APZ to make sure any repaint requests have been sent
+ // back to the main thread
+ flushApzRepaints(function() {
+ // Then flush the main-thread again to process the repaint requests.
+ // Once this is done, we should be in a stable state with nothing
+ // pending, so we can trigger the callback.
+ waitForAllPaints(aCallback);
+ });
+ });
+}
+
+// This function takes a set of subtests to run one at a time in new top-level
+// windows, and returns a Promise that is resolved once all the subtests are
+// done running.
+//
+// The aSubtests array is an array of objects with the following keys:
+// file: required, the filename of the subtest.
+// prefs: optional, an array of arrays containing key-value prefs to set.
+// dp_suppression: optional, a boolean on whether or not to respect displayport
+// suppression during the test.
+// onload: optional, a function that will be registered as a load event listener
+// for the child window that will hold the subtest. the function will be
+// passed exactly one argument, which will be the child window.
+// An example of an array is:
+// aSubtests = [
+// { 'file': 'test_file_name.html' },
+// { 'file': 'test_file_2.html', 'prefs': [['pref.name', true], ['other.pref', 1000]], 'dp_suppression': false }
+// { 'file': 'file_3.html', 'onload': function(w) { w.subtestDone(); } }
+// ];
+//
+// Each subtest should call the subtestDone() function when it is done, to
+// indicate that the window should be torn down and the next text should run.
+// The subtestDone() function is injected into the subtest's window by this
+// function prior to loading the subtest. For convenience, the |is| and |ok|
+// functions provided by SimpleTest are also mapped into the subtest's window.
+// For other things from the parent, the subtest can use window.opener.<whatever>
+// to access objects.
+function runSubtestsSeriallyInFreshWindows(aSubtests) {
+ return new Promise(function(resolve, reject) {
+ var testIndex = -1;
+ var w = null;
+
+ function advanceSubtestExecution() {
+ var test = aSubtests[testIndex];
+ if (w) {
+ if (typeof test.dp_suppression != 'undefined') {
+ // We modified the suppression when starting the test, so now undo that.
+ SpecialPowers.getDOMWindowUtils(window).respectDisplayPortSuppression(!test.dp_suppression);
+ }
+ if (test.prefs) {
+ // We pushed some prefs for this test, pop them, and re-invoke
+ // advanceSubtestExecution() after that's been processed
+ SpecialPowers.popPrefEnv(function() {
+ w.close();
+ w = null;
+ advanceSubtestExecution();
+ });
+ return;
+ }
+
+ w.close();
+ }
+
+ testIndex++;
+ if (testIndex >= aSubtests.length) {
+ resolve();
+ return;
+ }
+
+ test = aSubtests[testIndex];
+ if (typeof test.dp_suppression != 'undefined') {
+ // Normally during a test, the displayport will get suppressed during page
+ // load, and unsuppressed at a non-deterministic time during the test. The
+ // unsuppression can trigger a repaint which interferes with the test, so
+ // to avoid that we can force the displayport to be unsuppressed for the
+ // entire test which is more deterministic.
+ SpecialPowers.getDOMWindowUtils(window).respectDisplayPortSuppression(test.dp_suppression);
+ }
+
+ function spawnTest(aFile) {
+ w = window.open('', "_blank");
+ w.subtestDone = advanceSubtestExecution;
+ w.SimpleTest = SimpleTest;
+ w.is = function(a, b, msg) { return is(a, b, aFile + " | " + msg); };
+ w.ok = function(cond, name, diag) { return ok(cond, aFile + " | " + name, diag); };
+ if (test.onload) {
+ w.addEventListener('load', function(e) { test.onload(w); }, { once: true });
+ }
+ w.location = location.href.substring(0, location.href.lastIndexOf('/') + 1) + aFile;
+ return w;
+ }
+
+ if (test.prefs) {
+ // Got some prefs for this subtest, push them
+ SpecialPowers.pushPrefEnv({"set": test.prefs}, function() {
+ w = spawnTest(test.file);
+ });
+ } else {
+ w = spawnTest(test.file);
+ }
+ }
+
+ advanceSubtestExecution();
+ });
+}
+
+function pushPrefs(prefs) {
+ return SpecialPowers.pushPrefEnv({'set': prefs});
+}
+
+function waitUntilApzStable() {
+ return new Promise(function(resolve, reject) {
+ SimpleTest.waitForFocus(function() {
+ waitForAllPaints(function() {
+ flushApzRepaints(resolve);
+ });
+ }, window);
+ });
+}
+
+function isApzEnabled() {
+ var enabled = SpecialPowers.getDOMWindowUtils(window).asyncPanZoomEnabled;
+ if (!enabled) {
+ // 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;
+}
+
+// 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:
+// runContinuation(myTest)();
+function runContinuation(testFunction) {
+ // We need to wrap this in an extra function, so that the call site can
+ // be more readable without running the promise too early. In other words,
+ // if we didn't have this extra function, the promise would start running
+ // during construction of the promise chain, concurrently with the first
+ // promise in the chain.
+ return function() {
+ return new Promise(function(resolve, reject) {
+ var testContinuation = null;
+
+ function driveTest() {
+ if (!testContinuation) {
+ testContinuation = testFunction(driveTest);
+ }
+ var ret = testContinuation.next();
+ if (ret.done) {
+ resolve();
+ }
+ }
+
+ driveTest();
+ });
+ };
+}
+
+// Take a snapshot of the given rect, *including compositor transforms* (i.e.
+// includes async scroll transforms applied by APZ). If you don't need the
+// compositor transforms, you can probably get away with using
+// SpecialPowers.snapshotWindowWithOptions or one of the friendlier wrappers.
+// The rect provided is expected to be relative to the screen, for example as
+// returned by rectRelativeToScreen in apz_test_native_event_utils.js.
+// Example usage:
+// var snapshot = getSnapshot(rectRelativeToScreen(myDiv));
+// which will take a snapshot of the 'myDiv' element. Note that if part of the
+// element is obscured by other things on top, the snapshot will include those
+// things. If it is clipped by a scroll container, the snapshot will include
+// that area anyway, so you will probably get parts of the scroll container in
+// the snapshot. If the rect extends outside the browser window then the
+// results are undefined.
+// The snapshot is returned in the form of a data URL.
+function getSnapshot(rect) {
+ function parentProcessSnapshot() {
+ addMessageListener('snapshot', function(rect) {
+ Components.utils.import('resource://gre/modules/Services.jsm');
+ var topWin = Services.wm.getMostRecentWindow('navigator:browser');
+
+ // reposition the rect relative to the top-level browser window
+ rect = JSON.parse(rect);
+ rect.x -= topWin.mozInnerScreenX;
+ rect.y -= topWin.mozInnerScreenY;
+
+ // take the snapshot
+ var canvas = topWin.document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
+ canvas.width = rect.w;
+ canvas.height = rect.h;
+ var ctx = canvas.getContext("2d");
+ ctx.drawWindow(topWin, rect.x, rect.y, rect.w, rect.h, 'rgb(255,255,255)', ctx.DRAWWINDOW_DRAW_VIEW | ctx.DRAWWINDOW_USE_WIDGET_LAYERS | ctx.DRAWWINDOW_DRAW_CARET);
+ return canvas.toDataURL();
+ });
+ }
+
+ if (typeof getSnapshot.chromeHelper == 'undefined') {
+ // This is the first time getSnapshot is being called; do initialization
+ getSnapshot.chromeHelper = SpecialPowers.loadChromeScript(parentProcessSnapshot);
+ SimpleTest.registerCleanupFunction(function() { getSnapshot.chromeHelper.destroy() });
+ }
+
+ return getSnapshot.chromeHelper.sendSyncMessage('snapshot', JSON.stringify(rect)).toString();
+}
+
+// Takes the document's query string and parses it, assuming the query string
+// is composed of key-value pairs where the value is in JSON format. The object
+// returned contains the various values indexed by their respective keys. In
+// case of duplicate keys, the last value be used.
+// Examples:
+// ?key="value"&key2=false&key3=500
+// produces { "key": "value", "key2": false, "key3": 500 }
+// ?key={"x":0,"y":50}&key2=[1,2,true]
+// produces { "key": { "x": 0, "y": 0 }, "key2": [1, 2, true] }
+function getQueryArgs() {
+ var args = {};
+ if (location.search.length > 0) {
+ var params = location.search.substr(1).split('&');
+ for (var p of params) {
+ var [k, v] = p.split('=');
+ args[k] = JSON.parse(v);
+ }
+ }
+ return args;
+}
+
+// Return a function that returns a promise to create a script element with the
+// given URI and append it to the head of the document in the given window.
+// As with runContinuation(), the extra function wrapper is for convenience
+// at the call site, so that this can be chained with other promises:
+// waitUntilApzStable().then(injectScript('foo'))
+// .then(injectScript('bar'));
+// If you want to do the injection right away, run the function returned by
+// this function:
+// injectScript('foo')();
+function injectScript(aScript, aWindow = window) {
+ return function() {
+ return new Promise(function(resolve, reject) {
+ var e = aWindow.document.createElement('script');
+ e.type = 'text/javascript';
+ e.onload = function() {
+ resolve();
+ };
+ e.onerror = function() {
+ dump('Script [' + aScript + '] errored out\n');
+ reject();
+ };
+ e.src = aScript;
+ aWindow.document.getElementsByTagName('head')[0].appendChild(e);
+ });
+ };
+}
diff --git a/gfx/layers/apz/test/mochitest/chrome.ini b/gfx/layers/apz/test/mochitest/chrome.ini
new file mode 100644
index 000000000..d52da5928
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/chrome.ini
@@ -0,0 +1,9 @@
+[DEFAULT]
+support-files =
+ apz_test_native_event_utils.js
+tags = apz-chrome
+
+[test_smoothness.html]
+# hardware vsync only on win/mac
+# e10s only since APZ is only enabled on e10s
+skip-if = debug || (os != 'mac' && os != 'win') || !e10s
diff --git a/gfx/layers/apz/test/mochitest/helper_basic_pan.html b/gfx/layers/apz/test/mochitest/helper_basic_pan.html
new file mode 100644
index 000000000..c33258da8
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/helper_basic_pan.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width; initial-scale=1.0">
+ <title>Sanity panning test</title>
+ <script type="application/javascript" src="apz_test_native_event_utils.js"></script>
+ <script type="application/javascript" src="apz_test_utils.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
+ <script type="application/javascript">
+
+function scrollPage() {
+ var transformEnd = function() {
+ SpecialPowers.Services.obs.removeObserver(transformEnd, "APZ:TransformEnd", false);
+ dump("Transform complete; flushing repaints...\n");
+ flushApzRepaints(checkScroll);
+ };
+ SpecialPowers.Services.obs.addObserver(transformEnd, "APZ:TransformEnd", false);
+
+ synthesizeNativeTouchDrag(document.body, 10, 100, 0, -(50 + TOUCH_SLOP));
+ dump("Finished native drag, waiting for transform-end observer...\n");
+}
+
+function checkScroll() {
+ is(window.scrollY, 50, "check that the window scrolled");
+ subtestDone();
+}
+
+waitUntilApzStable().then(scrollPage);
+
+ </script>
+</head>
+<body>
+ <div style="height: 5000px; background-color: lightgreen;">
+ This div makes the page scrollable.
+ </div>
+</body>
+</html>
diff --git a/gfx/layers/apz/test/mochitest/helper_bug1151663.html b/gfx/layers/apz/test/mochitest/helper_bug1151663.html
new file mode 100644
index 000000000..ef2fde9a9
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/helper_bug1151663.html
@@ -0,0 +1,83 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1151663
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1151663, helper page</title>
+ <script type="application/javascript" src="apz_test_utils.js"></script>
+ <script type="application/javascript">
+
+ // -------------------------------------------------------------------
+ // Infrastructure to get the test assertions to run at the right time.
+ // -------------------------------------------------------------------
+ var SimpleTest = window.opener.SimpleTest;
+
+ window.onload = function() {
+ window.addEventListener("MozAfterPaint", afterPaint, false);
+ };
+ var utils = SpecialPowers.getDOMWindowUtils(window);
+ function afterPaint(e) {
+ // If there is another paint pending, wait for it.
+ if (utils.isMozAfterPaintPending) {
+ return;
+ }
+
+ // Once there are no more paints pending, remove the
+ // MozAfterPaint listener and run the test logic.
+ window.removeEventListener("MozAfterPaint", afterPaint, false);
+ testBug1151663();
+ }
+
+ // --------------------------------------------------------------------
+ // The actual logic for testing bug 1151663.
+ //
+ // In this test we have a simple page which is scrollable, with a
+ // scrollable <div> which is also scrollable. We test that the
+ // <div> does not get an initial APZC, since primary scrollable
+ // frame is the page's root scroll frame.
+ // --------------------------------------------------------------------
+
+ function testBug1151663() {
+ // Get the content- and compositor-side test data from nsIDOMWindowUtils.
+ var contentTestData = utils.getContentAPZTestData();
+ var compositorTestData = utils.getCompositorAPZTestData();
+
+ // 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.
+ SimpleTest.ok(compositorTestData.paints.length > 0,
+ "expected at least one paint in compositor test data");
+ var lastCompositorPaint = compositorTestData.paints[compositorTestData.paints.length - 1];
+ var lastCompositorPaintSeqNo = lastCompositorPaint.sequenceNumber;
+
+ // Convert the test data into a representation that's easier to navigate.
+ contentTestData = convertTestData(contentTestData);
+ compositorTestData = convertTestData(compositorTestData);
+ var paint = compositorTestData.paints[lastCompositorPaintSeqNo];
+
+ // Reconstruct the APZC tree structure in the last paint.
+ var apzcTree = buildApzcTree(paint);
+
+ // The apzc tree for this page should consist of a single root APZC,
+ // which either is the RCD with no child APZCs (e10s/B2G case) or has a
+ // single child APZC which is for the RCD (fennec case).
+ var rcd = findRcdNode(apzcTree);
+ SimpleTest.ok(rcd != null, "found the RCD node");
+ SimpleTest.is(rcd.children.length, 0, "expected no children on the RCD");
+
+ window.opener.finishTest();
+ }
+ </script>
+</head>
+<body style="height: 500px; overflow: scroll">
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1151663">Mozilla Bug 1151663</a>
+ <div style="height: 50px; width: 50px; overflow: scroll">
+ <!-- Put enough content into the subframe to make it have a nonzero scroll range -->
+ <div style="height: 100px; width: 50px"></div>
+ </div>
+ <!-- Put enough content into the page to make it have a nonzero scroll range -->
+ <div style="height: 1000px"></div>
+</body>
+</html>
diff --git a/gfx/layers/apz/test/mochitest/helper_bug1162771.html b/gfx/layers/apz/test/mochitest/helper_bug1162771.html
new file mode 100644
index 000000000..18e4a2f05
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/helper_bug1162771.html
@@ -0,0 +1,104 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width; initial-scale=1.0">
+ <title>Test for touchend on media elements</title>
+ <script type="application/javascript" src="apz_test_native_event_utils.js"></script>
+ <script type="application/javascript" src="apz_test_utils.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
+ <script type="application/javascript">
+
+function* test(testDriver) {
+ var v = document.getElementById('video');
+ var a = document.getElementById('audio');
+ var d = document.getElementById('div');
+
+ document.body.ontouchstart = function(e) {
+ if (e.target === v || e.target === a || e.target === d) {
+ e.target.style.display = 'none';
+ ok(true, 'Set display to none on #' + e.target.id);
+ } else {
+ ok(false, 'Got unexpected touchstart on ' + e.target);
+ }
+ waitForAllPaints(testDriver);
+ };
+
+ document.body.ontouchend = function(e) {
+ if (e.target === v || e.target === a || e.target === d) {
+ e.target._gotTouchend = true;
+ ok(true, 'Got touchend event on #' + e.target.id);
+ }
+ testDriver();
+ };
+
+ var utils = SpecialPowers.getDOMWindowUtils(window);
+
+ var pt = coordinatesRelativeToScreen(25, 5, v);
+ yield utils.sendNativeTouchPoint(0, SpecialPowers.DOMWindowUtils.TOUCH_CONTACT, pt.x, pt.y, 1, 90, null);
+ yield utils.sendNativeTouchPoint(0, SpecialPowers.DOMWindowUtils.TOUCH_REMOVE, pt.x, pt.y, 1, 90, null);
+ ok(v._gotTouchend, 'Touchend was received on video element');
+
+ pt = coordinatesRelativeToScreen(25, 5, a);
+ yield utils.sendNativeTouchPoint(0, SpecialPowers.DOMWindowUtils.TOUCH_CONTACT, pt.x, pt.y, 1, 90, null);
+ yield utils.sendNativeTouchPoint(0, SpecialPowers.DOMWindowUtils.TOUCH_REMOVE, pt.x, pt.y, 1, 90, null);
+ ok(a._gotTouchend, 'Touchend was received on audio element');
+
+ pt = coordinatesRelativeToScreen(25, 5, d);
+ yield utils.sendNativeTouchPoint(0, SpecialPowers.DOMWindowUtils.TOUCH_CONTACT, pt.x, pt.y, 1, 90, null);
+ yield utils.sendNativeTouchPoint(0, SpecialPowers.DOMWindowUtils.TOUCH_REMOVE, pt.x, pt.y, 1, 90, null);
+ ok(d._gotTouchend, 'Touchend was received on div element');
+}
+
+waitUntilApzStable()
+.then(runContinuation(test))
+.then(subtestDone);
+
+ </script>
+ <style>
+ * {
+ font-size: 24px;
+ box-sizing: border-box;
+ }
+
+ #video {
+ display:block;
+ position:absolute;
+ top: 100px;
+ left:0;
+ width: 33%;
+ height: 100px;
+ border:solid black 1px;
+ background-color: #8a8;
+ }
+
+ #audio {
+ display:block;
+ position:absolute;
+ top: 100px;
+ left:33%;
+ width: 33%;
+ height: 100px;
+ border:solid black 1px;
+ background-color: #a88;
+ }
+
+ #div {
+ display:block;
+ position:absolute;
+ top: 100px;
+ left: 66%;
+ width: 34%;
+ height: 100px;
+ border:solid black 1px;
+ background-color: #88a;
+ }
+ </style>
+</head>
+<body>
+ <p>Tap on the colored boxes to hide them.</p>
+ <video id="video"></video>
+ <audio id="audio" controls></audio>
+ <div id="div"></div>
+</body>
+</html>
diff --git a/gfx/layers/apz/test/mochitest/helper_bug1271432.html b/gfx/layers/apz/test/mochitest/helper_bug1271432.html
new file mode 100644
index 000000000..8234b8232
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/helper_bug1271432.html
@@ -0,0 +1,574 @@
+<head>
+ <title>Ensure that the hit region doesn't get unexpectedly expanded</title>
+ <script type="application/javascript" src="apz_test_native_event_utils.js"></script>
+ <script type="application/javascript" src="apz_test_utils.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
+<script type="application/javascript">
+function* test(testDriver) {
+ var scroller = document.getElementById('scroller');
+ var scrollerPos = scroller.scrollTop;
+ var dx = 100, dy = 50;
+
+ is(window.scrollY, 0, "Initial page scroll position should be 0");
+ is(scrollerPos, 0, "Initial scroller position should be 0");
+
+ yield moveMouseAndScrollWheelOver(scroller, dx, dy, testDriver);
+
+ is(window.scrollY, 0, "Page scroll position should still be 0");
+ ok(scroller.scrollTop > scrollerPos, "Scroller should have scrolled");
+
+ // wait for it to layerize fully and then try again
+ yield waitForAllPaints(function() {
+ flushApzRepaints(testDriver);
+ });
+ scrollerPos = scroller.scrollTop;
+
+ yield moveMouseAndScrollWheelOver(scroller, dx, dy, testDriver);
+ is(window.scrollY, 0, "Page scroll position should still be 0 after layerization");
+ ok(scroller.scrollTop > scrollerPos, "Scroller should have continued scrolling");
+}
+
+waitUntilApzStable()
+.then(runContinuation(test))
+.then(subtestDone);
+
+</script>
+<style>
+a#with_after_content {
+ background-color: #F16725;
+ opacity: 0.8;
+ display: inline-block;
+ margin-top: 40px;
+ margin-left: 40px;
+}
+a#with_after_content::after {
+ content: " ";
+ position: absolute;
+ width: 0px;
+ height: 0px;
+ bottom: 40px;
+ z-index: -1;
+ right: 40px;
+ background-color: transparent;
+ border-style: solid;
+ border-width: 15px 15px 15px 0;
+ border-color: #d54e0e transparent transparent transparent;
+ box-shadow: none;
+ box-sizing: border-box;
+}
+div#scroller {
+ overflow-y: scroll;
+ width: 50%;
+ height: 50%;
+}
+</style>
+</head>
+<body>
+<a id="with_after_content">Some text</a>
+
+<div id="scroller">
+Scrolling on the very left edge of this div will work.
+Scrolling on the right side of this div (starting with the left edge of the orange box above) should work, but doesn't.<br/>
+0<br>
+1<br>
+2<br>
+3<br>
+4<br>
+5<br>
+6<br>
+7<br>
+8<br>
+9<br>
+10<br>
+11<br>
+12<br>
+13<br>
+14<br>
+15<br>
+16<br>
+17<br>
+18<br>
+19<br>
+20<br>
+21<br>
+22<br>
+23<br>
+24<br>
+25<br>
+26<br>
+27<br>
+28<br>
+29<br>
+30<br>
+31<br>
+32<br>
+33<br>
+34<br>
+35<br>
+36<br>
+37<br>
+38<br>
+39<br>
+40<br>
+41<br>
+42<br>
+43<br>
+44<br>
+45<br>
+46<br>
+47<br>
+48<br>
+49<br>
+50<br>
+51<br>
+52<br>
+53<br>
+54<br>
+55<br>
+56<br>
+57<br>
+58<br>
+59<br>
+60<br>
+61<br>
+62<br>
+63<br>
+64<br>
+65<br>
+66<br>
+67<br>
+68<br>
+69<br>
+70<br>
+71<br>
+72<br>
+73<br>
+74<br>
+75<br>
+76<br>
+77<br>
+78<br>
+79<br>
+80<br>
+81<br>
+82<br>
+83<br>
+84<br>
+85<br>
+86<br>
+87<br>
+88<br>
+89<br>
+90<br>
+91<br>
+92<br>
+93<br>
+94<br>
+95<br>
+96<br>
+97<br>
+98<br>
+99<br>
+100<br>
+101<br>
+102<br>
+103<br>
+104<br>
+105<br>
+106<br>
+107<br>
+108<br>
+109<br>
+110<br>
+111<br>
+112<br>
+113<br>
+114<br>
+115<br>
+116<br>
+117<br>
+118<br>
+119<br>
+120<br>
+121<br>
+122<br>
+123<br>
+124<br>
+125<br>
+126<br>
+127<br>
+128<br>
+129<br>
+130<br>
+131<br>
+132<br>
+133<br>
+134<br>
+135<br>
+136<br>
+137<br>
+138<br>
+139<br>
+140<br>
+141<br>
+142<br>
+143<br>
+144<br>
+145<br>
+146<br>
+147<br>
+148<br>
+149<br>
+150<br>
+151<br>
+152<br>
+153<br>
+154<br>
+155<br>
+156<br>
+157<br>
+158<br>
+159<br>
+160<br>
+161<br>
+162<br>
+163<br>
+164<br>
+165<br>
+166<br>
+167<br>
+168<br>
+169<br>
+170<br>
+171<br>
+172<br>
+173<br>
+174<br>
+175<br>
+176<br>
+177<br>
+178<br>
+179<br>
+180<br>
+181<br>
+182<br>
+183<br>
+184<br>
+185<br>
+186<br>
+187<br>
+188<br>
+189<br>
+190<br>
+191<br>
+192<br>
+193<br>
+194<br>
+195<br>
+196<br>
+197<br>
+198<br>
+199<br>
+200<br>
+201<br>
+202<br>
+203<br>
+204<br>
+205<br>
+206<br>
+207<br>
+208<br>
+209<br>
+210<br>
+211<br>
+212<br>
+213<br>
+214<br>
+215<br>
+216<br>
+217<br>
+218<br>
+219<br>
+220<br>
+221<br>
+222<br>
+223<br>
+224<br>
+225<br>
+226<br>
+227<br>
+228<br>
+229<br>
+230<br>
+231<br>
+232<br>
+233<br>
+234<br>
+235<br>
+236<br>
+237<br>
+238<br>
+239<br>
+240<br>
+241<br>
+242<br>
+243<br>
+244<br>
+245<br>
+246<br>
+247<br>
+248<br>
+249<br>
+250<br>
+251<br>
+252<br>
+253<br>
+254<br>
+255<br>
+256<br>
+257<br>
+258<br>
+259<br>
+260<br>
+261<br>
+262<br>
+263<br>
+264<br>
+265<br>
+266<br>
+267<br>
+268<br>
+269<br>
+270<br>
+271<br>
+272<br>
+273<br>
+274<br>
+275<br>
+276<br>
+277<br>
+278<br>
+279<br>
+280<br>
+281<br>
+282<br>
+283<br>
+284<br>
+285<br>
+286<br>
+287<br>
+288<br>
+289<br>
+290<br>
+291<br>
+292<br>
+293<br>
+294<br>
+295<br>
+296<br>
+297<br>
+298<br>
+299<br>
+300<br>
+301<br>
+302<br>
+303<br>
+304<br>
+305<br>
+306<br>
+307<br>
+308<br>
+309<br>
+310<br>
+311<br>
+312<br>
+313<br>
+314<br>
+315<br>
+316<br>
+317<br>
+318<br>
+319<br>
+320<br>
+321<br>
+322<br>
+323<br>
+324<br>
+325<br>
+326<br>
+327<br>
+328<br>
+329<br>
+330<br>
+331<br>
+332<br>
+333<br>
+334<br>
+335<br>
+336<br>
+337<br>
+338<br>
+339<br>
+340<br>
+341<br>
+342<br>
+343<br>
+344<br>
+345<br>
+346<br>
+347<br>
+348<br>
+349<br>
+350<br>
+351<br>
+352<br>
+353<br>
+354<br>
+355<br>
+356<br>
+357<br>
+358<br>
+359<br>
+360<br>
+361<br>
+362<br>
+363<br>
+364<br>
+365<br>
+366<br>
+367<br>
+368<br>
+369<br>
+370<br>
+371<br>
+372<br>
+373<br>
+374<br>
+375<br>
+376<br>
+377<br>
+378<br>
+379<br>
+380<br>
+381<br>
+382<br>
+383<br>
+384<br>
+385<br>
+386<br>
+387<br>
+388<br>
+389<br>
+390<br>
+391<br>
+392<br>
+393<br>
+394<br>
+395<br>
+396<br>
+397<br>
+398<br>
+399<br>
+400<br>
+401<br>
+402<br>
+403<br>
+404<br>
+405<br>
+406<br>
+407<br>
+408<br>
+409<br>
+410<br>
+411<br>
+412<br>
+413<br>
+414<br>
+415<br>
+416<br>
+417<br>
+418<br>
+419<br>
+420<br>
+421<br>
+422<br>
+423<br>
+424<br>
+425<br>
+426<br>
+427<br>
+428<br>
+429<br>
+430<br>
+431<br>
+432<br>
+433<br>
+434<br>
+435<br>
+436<br>
+437<br>
+438<br>
+439<br>
+440<br>
+441<br>
+442<br>
+443<br>
+444<br>
+445<br>
+446<br>
+447<br>
+448<br>
+449<br>
+450<br>
+451<br>
+452<br>
+453<br>
+454<br>
+455<br>
+456<br>
+457<br>
+458<br>
+459<br>
+460<br>
+461<br>
+462<br>
+463<br>
+464<br>
+465<br>
+466<br>
+467<br>
+468<br>
+469<br>
+470<br>
+471<br>
+472<br>
+473<br>
+474<br>
+475<br>
+476<br>
+477<br>
+478<br>
+479<br>
+480<br>
+481<br>
+482<br>
+483<br>
+484<br>
+485<br>
+486<br>
+487<br>
+488<br>
+489<br>
+490<br>
+491<br>
+492<br>
+493<br>
+494<br>
+495<br>
+496<br>
+497<br>
+498<br>
+499<br>
+</div>
+<div style="height: 1000px">this div makes the page scrollable</div>
+</body>
diff --git a/gfx/layers/apz/test/mochitest/helper_bug1280013.html b/gfx/layers/apz/test/mochitest/helper_bug1280013.html
new file mode 100644
index 000000000..0c602901a
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/helper_bug1280013.html
@@ -0,0 +1,74 @@
+<!DOCTYPE HTML>
+<html style="overflow:hidden">
+<head>
+ <meta charset="utf-8">
+ <!-- The viewport tag will result in APZ being in a "zoomed-in" state, assuming
+ the device width is less than 980px. -->
+ <meta name="viewport" content="width=980; initial-scale=1.0">
+ <title>Test for bug 1280013</title>
+ <script type="application/javascript" src="apz_test_native_event_utils.js"></script>
+ <script type="application/javascript" src="apz_test_utils.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
+ <script type="application/javascript">
+function* test(testDriver) {
+ SimpleTest.ok(screen.height > 500, "Screen height must be at least 500 pixels for this test to work");
+
+ // This listener will trigger the test to continue once APZ is done with
+ // processing the scroll.
+ SpecialPowers.Services.obs.addObserver(testDriver, "APZ:TransformEnd", false);
+
+ // Scroll down to the iframe. Do it in two drags instead of one in case the
+ // device screen is short
+ yield synthesizeNativeTouchDrag(document.body, 10, 400, 0, -(350 + TOUCH_SLOP));
+ yield synthesizeNativeTouchDrag(document.body, 10, 400, 0, -(350 + TOUCH_SLOP));
+ // Now the top of the visible area should be at y=700 of the top-level page,
+ // so if the screen is >= 500px tall, the entire iframe should be visible, at
+ // least vertically.
+
+ // However, because of the overflow:hidden on the root elements, all this
+ // scrolling is happening in APZ and is not reflected in the main-thread
+ // scroll position (it is stored in the callback transform instead). We check
+ // this by checking the scroll offset.
+ yield flushApzRepaints(testDriver);
+ SimpleTest.is(window.scrollY, 0, "Main-thread scroll position is still at 0");
+
+ // Scroll the iframe by 300px. Note that since the main-thread scroll position
+ // is still 0, the subframe's getBoundingClientRect is going to be off by
+ // 700 pixels, so we compensate for that here.
+ var subframe = document.getElementById('subframe');
+ yield synthesizeNativeTouchDrag(subframe, 10, 200 - 700, 0, -(300 + TOUCH_SLOP));
+
+ // Remove the observer, we don't need it any more.
+ SpecialPowers.Services.obs.removeObserver(testDriver, "APZ:TransformEnd", false);
+
+ // Flush any pending paints
+ yield flushApzRepaints(testDriver);
+
+ // get the displayport for the subframe
+ var utils = SpecialPowers.getDOMWindowUtils(window);
+ var contentPaints = utils.getContentAPZTestData().paints;
+ var lastPaint = convertScrollFrameData(contentPaints[contentPaints.length - 1].scrollFrames);
+ var foundIt = 0;
+ for (var scrollId in lastPaint) {
+ if (('contentDescription' in lastPaint[scrollId]) &&
+ (lastPaint[scrollId]['contentDescription'].includes('tall_html'))) {
+ var dp = getPropertyAsRect(lastPaint, scrollId, 'criticalDisplayport');
+ SimpleTest.ok(dp.y <= 0, 'The critical displayport top should be less than or equal to zero to cover the visible part of the subframe; it is ' + dp.y);
+ SimpleTest.ok(dp.y + dp.h >= subframe.clientHeight, 'The critical displayport bottom should be greater than the clientHeight; it is ' + (dp.y + dp.h));
+ foundIt++;
+ }
+ }
+ SimpleTest.is(foundIt, 1, "Found exactly one critical displayport for the subframe we were interested in.");
+}
+
+waitUntilApzStable()
+.then(runContinuation(test))
+.then(subtestDone);
+
+ </script>
+</head>
+<body style="overflow:hidden">
+ The iframe below is at (0, 800). Scroll it into view, and then scroll the contents. The content should be fully rendered in high-resolution.
+ <iframe id="subframe" style="position:absolute; left: 0px; top: 800px; width: 600px; height: 350px" src="helper_tall.html"></iframe>
+</body>
+</html>
diff --git a/gfx/layers/apz/test/mochitest/helper_bug1285070.html b/gfx/layers/apz/test/mochitest/helper_bug1285070.html
new file mode 100644
index 000000000..3a4879034
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/helper_bug1285070.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width; initial-scale=1.0">
+ <title>Test pointer events are dispatched once for touch tap</title>
+ <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" src="apz_test_native_event_utils.js"></script>
+ <script type="application/javascript">
+ function test() {
+ let pointerEventsList = ["pointerover", "pointerenter", "pointerdown",
+ "pointerup", "pointerleave", "pointerout"];
+ let pointerEventsCount = {};
+
+ pointerEventsList.forEach((eventName) => {
+ pointerEventsCount[eventName] = 0;
+ document.getElementById('div1').addEventListener(eventName, (event) => {
+ dump("Received event " + event.type + "\n");
+ ++pointerEventsCount[event.type];
+ }, false);
+ });
+
+ document.addEventListener("click", (event) => {
+ is(event.target, document.getElementById('div1'), "Clicked on div (at " + event.clientX + "," + event.clientY + ")");
+ for (var key in pointerEventsCount) {
+ is(pointerEventsCount[key], 1, "Event " + key + " should be generated once");
+ }
+ subtestDone();
+ }, false);
+
+ synthesizeNativeTap(document.getElementById('div1'), 100, 100, () => {
+ dump("Finished synthesizing tap, waiting for div to be clicked...\n");
+ });
+ }
+
+ waitUntilApzStable().then(test);
+
+ </script>
+</head>
+<body>
+ <div id="div1" style="width: 200px; height: 200px; background: black"></div>
+</body>
+</html>
diff --git a/gfx/layers/apz/test/mochitest/helper_bug1299195.html b/gfx/layers/apz/test/mochitest/helper_bug1299195.html
new file mode 100644
index 000000000..8e746749c
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/helper_bug1299195.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width; initial-scale=1.0">
+ <title>Test pointer events are dispatched once for touch tap</title>
+ <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" src="apz_test_native_event_utils.js"></script>
+ <script type="application/javascript">
+ /** Test for Bug 1299195 **/
+ function runTests() {
+ let target0 = document.getElementById("target0");
+ let mouseup_count = 0;
+ let mousedown_count = 0;
+ let pointerup_count = 0;
+ let pointerdown_count = 0;
+
+ target0.addEventListener("mouseup", () => {
+ ++mouseup_count;
+ if (mouseup_count == 2) {
+ is(mousedown_count, 2, "Double tap with touch should fire 2 mousedown events");
+ is(mouseup_count, 2, "Double tap with touch should fire 2 mouseup events");
+ is(pointerdown_count, 2, "Double tap with touch should fire 2 pointerdown events");
+ is(pointerup_count, 2, "Double tap with touch should fire 2 pointerup events");
+ subtestDone();
+ }
+ });
+ target0.addEventListener("mousedown", () => {
+ ++mousedown_count;
+ });
+ target0.addEventListener("pointerup", () => {
+ ++pointerup_count;
+ });
+ target0.addEventListener("pointerdown", () => {
+ ++pointerdown_count;
+ });
+ synthesizeNativeTap(document.getElementById('target0'), 100, 100);
+ synthesizeNativeTap(document.getElementById('target0'), 100, 100);
+ }
+ waitUntilApzStable().then(runTests);
+ </script>
+</head>
+<body>
+ <div id="target0" style="width: 200px; height: 200px; background: green"></div>
+</body>
+</html>
diff --git a/gfx/layers/apz/test/mochitest/helper_bug982141.html b/gfx/layers/apz/test/mochitest/helper_bug982141.html
new file mode 100644
index 000000000..5d2f15397
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/helper_bug982141.html
@@ -0,0 +1,149 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=982141
+-->
+<head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="user-scalable=no">
+ <title>Test for Bug 982141, helper page</title>
+ <script type="application/javascript" src="apz_test_utils.js"></script>
+ <script type="application/javascript">
+
+ // -------------------------------------------------------------------
+ // Infrastructure to get the test assertions to run at the right time.
+ // -------------------------------------------------------------------
+ var SimpleTest = window.opener.SimpleTest;
+
+ window.onload = function() {
+ window.addEventListener("MozAfterPaint", afterPaint, false);
+ };
+ var utils = SpecialPowers.getDOMWindowUtils(window);
+ function afterPaint(e) {
+ // If there is another paint pending, wait for it.
+ if (utils.isMozAfterPaintPending) {
+ return;
+ }
+
+ // Once there are no more paints pending, remove the
+ // MozAfterPaint listener and run the test logic.
+ window.removeEventListener("MozAfterPaint", afterPaint, false);
+ testBug982141();
+ }
+
+ // --------------------------------------------------------------------
+ // The actual logic for testing bug 982141.
+ //
+ // In this test we have a simple page with a scrollable <div> which has
+ // enough content to make it scrollable. We test that this <div> got
+ // a displayport.
+ // --------------------------------------------------------------------
+
+ function testBug982141() {
+ // Get the content- and compositor-side test data from nsIDOMWindowUtils.
+ var contentTestData = utils.getContentAPZTestData();
+ var compositorTestData = utils.getCompositorAPZTestData();
+
+ // 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.
+ SimpleTest.ok(compositorTestData.paints.length > 0,
+ "expected at least one paint in compositor test data");
+ var lastCompositorPaint = compositorTestData.paints[compositorTestData.paints.length - 1];
+ var lastCompositorPaintSeqNo = lastCompositorPaint.sequenceNumber;
+
+ // Convert the test data into a representation that's easier to navigate.
+ contentTestData = convertTestData(contentTestData);
+ compositorTestData = convertTestData(compositorTestData);
+
+ // Reconstruct the APZC tree structure in the last paint.
+ var apzcTree = buildApzcTree(compositorTestData.paints[lastCompositorPaintSeqNo]);
+
+ // The apzc tree for this page should consist of a single child APZC on
+ // the RCD node (the child is for scrollable <div>). Note that in e10s/B2G
+ // cases the RCD will be the root of the tree but on Fennec it will not.
+ var rcd = findRcdNode(apzcTree);
+ SimpleTest.ok(rcd != null, "found the RCD node");
+ SimpleTest.is(rcd.children.length, 1, "expected a single child APZC");
+ var childScrollId = rcd.children[0].scrollId;
+
+ // We should have content-side data for the same paint.
+ SimpleTest.ok(lastCompositorPaintSeqNo in contentTestData.paints,
+ "expected a content paint with sequence number" + lastCompositorPaintSeqNo);
+ var correspondingContentPaint = contentTestData.paints[lastCompositorPaintSeqNo];
+
+ var dp = getPropertyAsRect(correspondingContentPaint, childScrollId, 'displayport');
+ var subframe = document.getElementById('subframe');
+ // The clientWidth and clientHeight may be less than 50 if there are scrollbars showing.
+ // In general they will be (50 - <scrollbarwidth>, 50 - <scrollbarheight>).
+ SimpleTest.ok(subframe.clientWidth > 0, "Expected a non-zero clientWidth, got: " + subframe.clientWidth);
+ SimpleTest.ok(subframe.clientHeight > 0, "Expected a non-zero clientHeight, got: " + subframe.clientHeight);
+ SimpleTest.ok(dp.w >= subframe.clientWidth && dp.h >= subframe.clientHeight,
+ "expected a displayport at least as large as the scrollable element, got " + JSON.stringify(dp));
+
+ window.opener.finishTest();
+ }
+ </script>
+</head>
+<body style="overflow: hidden;"><!-- This combined with the user-scalable=no ensures the root frame is not scrollable -->
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=982141">Mozilla Bug 982141</a>
+ <!-- A scrollable subframe, with enough content to make it have a nonzero scroll range -->
+ <div id="subframe" style="height: 50px; width: 50px; overflow: scroll">
+ <div style="width: 100px">
+ Wide content so that the vertical scrollbar for the parent div
+ doesn't eat into the 50px width and reduce the width of the
+ displayport.
+ </div>
+ Line 1<br>
+ Line 2<br>
+ Line 3<br>
+ Line 4<br>
+ Line 5<br>
+ Line 6<br>
+ Line 7<br>
+ Line 8<br>
+ Line 9<br>
+ Line 10<br>
+ Line 11<br>
+ Line 12<br>
+ Line 13<br>
+ Line 14<br>
+ Line 15<br>
+ Line 16<br>
+ Line 17<br>
+ Line 18<br>
+ Line 19<br>
+ Line 20<br>
+ Line 21<br>
+ Line 22<br>
+ Line 23<br>
+ Line 24<br>
+ Line 25<br>
+ Line 26<br>
+ Line 27<br>
+ Line 28<br>
+ Line 29<br>
+ Line 30<br>
+ Line 31<br>
+ Line 32<br>
+ Line 33<br>
+ Line 34<br>
+ Line 35<br>
+ Line 36<br>
+ Line 37<br>
+ Line 38<br>
+ Line 39<br>
+ Line 40<br>
+ Line 41<br>
+ Line 42<br>
+ Line 43<br>
+ Line 44<br>
+ Line 45<br>
+ Line 46<br>
+ Line 40<br>
+ Line 48<br>
+ Line 49<br>
+ Line 50<br>
+ </div>
+</body>
+</html>
diff --git a/gfx/layers/apz/test/mochitest/helper_click.html b/gfx/layers/apz/test/mochitest/helper_click.html
new file mode 100644
index 000000000..b74f175fe
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/helper_click.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width; initial-scale=1.0">
+ <title>Sanity mouse-clicking test</title>
+ <script type="application/javascript" src="apz_test_native_event_utils.js"></script>
+ <script type="application/javascript" src="apz_test_utils.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
+ <script type="application/javascript">
+
+function* clickButton(testDriver) {
+ document.addEventListener('click', clicked, false);
+
+ if (getQueryArgs()['dtc']) {
+ // force a dispatch-to-content region on the document
+ document.addEventListener('wheel', function() { /* no-op */ }, { passive: false });
+ yield waitForAllPaints(function() {
+ flushApzRepaints(testDriver);
+ });
+ }
+
+ synthesizeNativeClick(document.getElementById('b'), 5, 5, function() {
+ dump("Finished synthesizing click, waiting for button to be clicked...\n");
+ });
+}
+
+function clicked(e) {
+ is(e.target, document.getElementById('b'), "Clicked on button, yay! (at " + e.clientX + "," + e.clientY + ")");
+ subtestDone();
+}
+
+waitUntilApzStable()
+.then(runContinuation(clickButton));
+
+ </script>
+</head>
+<body>
+ <button id="b" style="width: 10px; height: 10px"></button>
+</body>
+</html>
diff --git a/gfx/layers/apz/test/mochitest/helper_div_pan.html b/gfx/layers/apz/test/mochitest/helper_div_pan.html
new file mode 100644
index 000000000..f37be8ba6
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/helper_div_pan.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width; initial-scale=1.0">
+ <title>Sanity panning test for scrollable div</title>
+ <script type="application/javascript" src="apz_test_native_event_utils.js"></script>
+ <script type="application/javascript" src="apz_test_utils.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
+ <script type="application/javascript">
+
+function scrollOuter() {
+ var transformEnd = function() {
+ SpecialPowers.Services.obs.removeObserver(transformEnd, "APZ:TransformEnd", false);
+ dump("Transform complete; flushing repaints...\n");
+ flushApzRepaints(checkScroll);
+ };
+ SpecialPowers.Services.obs.addObserver(transformEnd, "APZ:TransformEnd", false);
+
+ synthesizeNativeTouchDrag(document.getElementById('outer'), 10, 100, 0, -(50 + TOUCH_SLOP));
+ dump("Finished native drag, waiting for transform-end observer...\n");
+}
+
+function checkScroll() {
+ var outerScroll = document.getElementById('outer').scrollTop;
+ is(outerScroll, 50, "check that the div scrolled");
+ subtestDone();
+}
+
+waitUntilApzStable().then(scrollOuter);
+
+ </script>
+</head>
+<body>
+ <div id="outer" style="height: 250px; border: solid 1px black; overflow:scroll">
+ <div style="height: 5000px; background-color: lightblue">
+ This div makes the |outer| div scrollable.
+ </div>
+ </div>
+ <div style="height: 5000px; background-color: lightgreen;">
+ This div makes the top-level page scrollable.
+ </div>
+</body>
+</html>
diff --git a/gfx/layers/apz/test/mochitest/helper_drag_click.html b/gfx/layers/apz/test/mochitest/helper_drag_click.html
new file mode 100644
index 000000000..cf7117339
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/helper_drag_click.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width; initial-scale=1.0">
+ <title>Sanity mouse-drag click test</title>
+ <script type="application/javascript" src="apz_test_native_event_utils.js"></script>
+ <script type="application/javascript" src="apz_test_utils.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
+ <script type="application/javascript">
+
+function* test(testDriver) {
+ document.addEventListener('click', clicked, false);
+
+ // Ensure the pointer is inside the window
+ yield synthesizeNativeMouseEvent(document.getElementById('b'), 5, 5, nativeMouseMoveEventMsg(), testDriver);
+ // mouse down, move it around, and release it near where it went down. this
+ // should generate a click at the release point
+ yield synthesizeNativeMouseEvent(document.getElementById('b'), 5, 5, nativeMouseDownEventMsg(), testDriver);
+ yield synthesizeNativeMouseEvent(document.getElementById('b'), 100, 100, nativeMouseMoveEventMsg(), testDriver);
+ yield synthesizeNativeMouseEvent(document.getElementById('b'), 10, 10, nativeMouseMoveEventMsg(), testDriver);
+ yield synthesizeNativeMouseEvent(document.getElementById('b'), 8, 8, nativeMouseUpEventMsg(), testDriver);
+ dump("Finished synthesizing click with a drag in the middle\n");
+}
+
+function clicked(e) {
+ // The mouse down at (5, 5) should not have generated a click, but the up
+ // at (8, 8) should have.
+ is(e.target, document.getElementById('b'), "Clicked on button, yay! (at " + e.clientX + "," + e.clientY + ")");
+ is(e.clientX, 8 + Math.floor(document.getElementById('b').getBoundingClientRect().left), 'x-coord of click event looks sane');
+ is(e.clientY, 8 + Math.floor(document.getElementById('b').getBoundingClientRect().top), 'y-coord of click event looks sane');
+ subtestDone();
+}
+
+waitUntilApzStable()
+.then(runContinuation(test));
+
+ </script>
+</head>
+<body>
+ <button id="b" style="width: 10px; height: 10px"></button>
+</body>
+</html>
diff --git a/gfx/layers/apz/test/mochitest/helper_drag_scroll.html b/gfx/layers/apz/test/mochitest/helper_drag_scroll.html
new file mode 100644
index 000000000..3c06a5b7e
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/helper_drag_scroll.html
@@ -0,0 +1,603 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width; initial-scale=1.0">
+ <title>Dragging the mouse on a content-implemented scrollbar</title>
+ <script type="application/javascript" src="apz_test_native_event_utils.js"></script>
+ <script type="application/javascript" src="apz_test_utils.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
+ <style>
+ body {
+ background: linear-gradient(135deg, red, blue);
+ }
+ #scrollbar {
+ position:fixed;
+ top: 0;
+ right: 10px;
+ height: 100%;
+ width: 150px;
+ background-color: gray;
+ }
+ </style>
+ <script type="text/javascript">
+var bar = null;
+var mouseDown = false;
+
+function moveTo(mouseY, testDriver) {
+ var fraction = (mouseY - bar.getBoundingClientRect().top) / bar.getBoundingClientRect().height;
+ fraction = Math.max(0, fraction);
+ fraction = Math.min(1, fraction);
+ var oldScrollPos = document.scrollingElement.scrollTop;
+ var newScrollPos = fraction * window.scrollMaxY;
+ SimpleTest.ok(newScrollPos > oldScrollPos, "Scroll position strictly increased");
+ // split the scroll in two with a paint in between, just to increase the
+ // complexity of the simulated web content, and to ensure this works as well.
+ document.scrollingElement.scrollTop = (oldScrollPos + newScrollPos) / 2;
+ waitForAllPaints(function() {
+ document.scrollingElement.scrollTop = newScrollPos;
+ testDriver();
+ });
+}
+
+function setupDragging(testDriver) {
+ bar = document.getElementById('scrollbar');
+ mouseDown = false;
+
+ bar.addEventListener('mousedown', function(e) {
+ mouseDown = true;
+ moveTo(e.clientY, testDriver);
+ }, true);
+
+ bar.addEventListener('mousemove', function(e) {
+ if (mouseDown) {
+ dump("Got mousemove clientY " + e.clientY + "\n");
+ moveTo(e.clientY, testDriver);
+ e.stopPropagation();
+ }
+ }, true);
+
+ bar.addEventListener('mouseup', function(e) {
+ mouseDown = false;
+ }, true);
+
+ window.addEventListener('mousemove', function(e) {
+ if (mouseDown) {
+ SimpleTest.ok(false, "The mousemove at " + e.clientY + " was not stopped by the bar listener, and is a glitchy event!");
+ setTimeout(testDriver, 0);
+ }
+ }, false);
+}
+
+function* test(testDriver) {
+ setupDragging(testDriver);
+
+ // Move the mouse to the "scrollbar" (the div upon which dragging changes scroll position)
+ yield synthesizeNativeMouseEvent(bar, 10, 10, nativeMouseMoveEventMsg(), testDriver);
+ // mouse down
+ yield synthesizeNativeMouseEvent(bar, 10, 10, nativeMouseDownEventMsg());
+ // drag vertically by 400px, in 50px increments
+ yield synthesizeNativeMouseEvent(bar, 10, 60, nativeMouseMoveEventMsg());
+ yield synthesizeNativeMouseEvent(bar, 10, 110, nativeMouseMoveEventMsg());
+ yield synthesizeNativeMouseEvent(bar, 10, 160, nativeMouseMoveEventMsg());
+ yield synthesizeNativeMouseEvent(bar, 10, 210, nativeMouseMoveEventMsg());
+ yield synthesizeNativeMouseEvent(bar, 10, 260, nativeMouseMoveEventMsg());
+ yield synthesizeNativeMouseEvent(bar, 10, 310, nativeMouseMoveEventMsg());
+ yield synthesizeNativeMouseEvent(bar, 10, 360, nativeMouseMoveEventMsg());
+ yield synthesizeNativeMouseEvent(bar, 10, 410, nativeMouseMoveEventMsg());
+ // and release
+ yield synthesizeNativeMouseEvent(bar, 10, 410, nativeMouseUpEventMsg(), testDriver);
+}
+
+waitUntilApzStable()
+.then(runContinuation(test))
+.then(subtestDone);
+
+ </script>
+</head>
+<body>
+
+<div id="scrollbar">Drag up and down on this bar. The background/scrollbar shouldn't glitch</div>
+This is a tall page<br/>
+1<br/>
+2<br/>
+3<br/>
+4<br/>
+5<br/>
+6<br/>
+7<br/>
+8<br/>
+9<br/>
+10<br/>
+11<br/>
+12<br/>
+13<br/>
+14<br/>
+15<br/>
+16<br/>
+17<br/>
+18<br/>
+19<br/>
+20<br/>
+21<br/>
+22<br/>
+23<br/>
+24<br/>
+25<br/>
+26<br/>
+27<br/>
+28<br/>
+29<br/>
+30<br/>
+31<br/>
+32<br/>
+33<br/>
+34<br/>
+35<br/>
+36<br/>
+37<br/>
+38<br/>
+39<br/>
+40<br/>
+41<br/>
+42<br/>
+43<br/>
+44<br/>
+45<br/>
+46<br/>
+47<br/>
+48<br/>
+49<br/>
+50<br/>
+51<br/>
+52<br/>
+53<br/>
+54<br/>
+55<br/>
+56<br/>
+57<br/>
+58<br/>
+59<br/>
+60<br/>
+61<br/>
+62<br/>
+63<br/>
+64<br/>
+65<br/>
+66<br/>
+67<br/>
+68<br/>
+69<br/>
+70<br/>
+71<br/>
+72<br/>
+73<br/>
+74<br/>
+75<br/>
+76<br/>
+77<br/>
+78<br/>
+79<br/>
+80<br/>
+81<br/>
+82<br/>
+83<br/>
+84<br/>
+85<br/>
+86<br/>
+87<br/>
+88<br/>
+89<br/>
+90<br/>
+91<br/>
+92<br/>
+93<br/>
+94<br/>
+95<br/>
+96<br/>
+97<br/>
+98<br/>
+99<br/>
+100<br/>
+101<br/>
+102<br/>
+103<br/>
+104<br/>
+105<br/>
+106<br/>
+107<br/>
+108<br/>
+109<br/>
+110<br/>
+111<br/>
+112<br/>
+113<br/>
+114<br/>
+115<br/>
+116<br/>
+117<br/>
+118<br/>
+119<br/>
+120<br/>
+121<br/>
+122<br/>
+123<br/>
+124<br/>
+125<br/>
+126<br/>
+127<br/>
+128<br/>
+129<br/>
+130<br/>
+131<br/>
+132<br/>
+133<br/>
+134<br/>
+135<br/>
+136<br/>
+137<br/>
+138<br/>
+139<br/>
+140<br/>
+141<br/>
+142<br/>
+143<br/>
+144<br/>
+145<br/>
+146<br/>
+147<br/>
+148<br/>
+149<br/>
+150<br/>
+151<br/>
+152<br/>
+153<br/>
+154<br/>
+155<br/>
+156<br/>
+157<br/>
+158<br/>
+159<br/>
+160<br/>
+161<br/>
+162<br/>
+163<br/>
+164<br/>
+165<br/>
+166<br/>
+167<br/>
+168<br/>
+169<br/>
+170<br/>
+171<br/>
+172<br/>
+173<br/>
+174<br/>
+175<br/>
+176<br/>
+177<br/>
+178<br/>
+179<br/>
+180<br/>
+181<br/>
+182<br/>
+183<br/>
+184<br/>
+185<br/>
+186<br/>
+187<br/>
+188<br/>
+189<br/>
+190<br/>
+191<br/>
+192<br/>
+193<br/>
+194<br/>
+195<br/>
+196<br/>
+197<br/>
+198<br/>
+199<br/>
+200<br/>
+201<br/>
+202<br/>
+203<br/>
+204<br/>
+205<br/>
+206<br/>
+207<br/>
+208<br/>
+209<br/>
+210<br/>
+211<br/>
+212<br/>
+213<br/>
+214<br/>
+215<br/>
+216<br/>
+217<br/>
+218<br/>
+219<br/>
+220<br/>
+221<br/>
+222<br/>
+223<br/>
+224<br/>
+225<br/>
+226<br/>
+227<br/>
+228<br/>
+229<br/>
+230<br/>
+231<br/>
+232<br/>
+233<br/>
+234<br/>
+235<br/>
+236<br/>
+237<br/>
+238<br/>
+239<br/>
+240<br/>
+241<br/>
+242<br/>
+243<br/>
+244<br/>
+245<br/>
+246<br/>
+247<br/>
+248<br/>
+249<br/>
+250<br/>
+251<br/>
+252<br/>
+253<br/>
+254<br/>
+255<br/>
+256<br/>
+257<br/>
+258<br/>
+259<br/>
+260<br/>
+261<br/>
+262<br/>
+263<br/>
+264<br/>
+265<br/>
+266<br/>
+267<br/>
+268<br/>
+269<br/>
+270<br/>
+271<br/>
+272<br/>
+273<br/>
+274<br/>
+275<br/>
+276<br/>
+277<br/>
+278<br/>
+279<br/>
+280<br/>
+281<br/>
+282<br/>
+283<br/>
+284<br/>
+285<br/>
+286<br/>
+287<br/>
+288<br/>
+289<br/>
+290<br/>
+291<br/>
+292<br/>
+293<br/>
+294<br/>
+295<br/>
+296<br/>
+297<br/>
+298<br/>
+299<br/>
+300<br/>
+301<br/>
+302<br/>
+303<br/>
+304<br/>
+305<br/>
+306<br/>
+307<br/>
+308<br/>
+309<br/>
+310<br/>
+311<br/>
+312<br/>
+313<br/>
+314<br/>
+315<br/>
+316<br/>
+317<br/>
+318<br/>
+319<br/>
+320<br/>
+321<br/>
+322<br/>
+323<br/>
+324<br/>
+325<br/>
+326<br/>
+327<br/>
+328<br/>
+329<br/>
+330<br/>
+331<br/>
+332<br/>
+333<br/>
+334<br/>
+335<br/>
+336<br/>
+337<br/>
+338<br/>
+339<br/>
+340<br/>
+341<br/>
+342<br/>
+343<br/>
+344<br/>
+345<br/>
+346<br/>
+347<br/>
+348<br/>
+349<br/>
+350<br/>
+351<br/>
+352<br/>
+353<br/>
+354<br/>
+355<br/>
+356<br/>
+357<br/>
+358<br/>
+359<br/>
+360<br/>
+361<br/>
+362<br/>
+363<br/>
+364<br/>
+365<br/>
+366<br/>
+367<br/>
+368<br/>
+369<br/>
+370<br/>
+371<br/>
+372<br/>
+373<br/>
+374<br/>
+375<br/>
+376<br/>
+377<br/>
+378<br/>
+379<br/>
+380<br/>
+381<br/>
+382<br/>
+383<br/>
+384<br/>
+385<br/>
+386<br/>
+387<br/>
+388<br/>
+389<br/>
+390<br/>
+391<br/>
+392<br/>
+393<br/>
+394<br/>
+395<br/>
+396<br/>
+397<br/>
+398<br/>
+399<br/>
+400<br/>
+401<br/>
+402<br/>
+403<br/>
+404<br/>
+405<br/>
+406<br/>
+407<br/>
+408<br/>
+409<br/>
+410<br/>
+411<br/>
+412<br/>
+413<br/>
+414<br/>
+415<br/>
+416<br/>
+417<br/>
+418<br/>
+419<br/>
+420<br/>
+421<br/>
+422<br/>
+423<br/>
+424<br/>
+425<br/>
+426<br/>
+427<br/>
+428<br/>
+429<br/>
+430<br/>
+431<br/>
+432<br/>
+433<br/>
+434<br/>
+435<br/>
+436<br/>
+437<br/>
+438<br/>
+439<br/>
+440<br/>
+441<br/>
+442<br/>
+443<br/>
+444<br/>
+445<br/>
+446<br/>
+447<br/>
+448<br/>
+449<br/>
+450<br/>
+451<br/>
+452<br/>
+453<br/>
+454<br/>
+455<br/>
+456<br/>
+457<br/>
+458<br/>
+459<br/>
+460<br/>
+461<br/>
+462<br/>
+463<br/>
+464<br/>
+465<br/>
+466<br/>
+467<br/>
+468<br/>
+469<br/>
+470<br/>
+471<br/>
+472<br/>
+473<br/>
+474<br/>
+475<br/>
+476<br/>
+477<br/>
+478<br/>
+479<br/>
+480<br/>
+481<br/>
+482<br/>
+483<br/>
+484<br/>
+485<br/>
+486<br/>
+487<br/>
+488<br/>
+489<br/>
+490<br/>
+491<br/>
+492<br/>
+493<br/>
+494<br/>
+495<br/>
+496<br/>
+497<br/>
+498<br/>
+499<br/>
+
+</body>
+</html>
diff --git a/gfx/layers/apz/test/mochitest/helper_iframe1.html b/gfx/layers/apz/test/mochitest/helper_iframe1.html
new file mode 100644
index 000000000..047da96bd
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/helper_iframe1.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<!-- The purpose of the 'id' on the HTML element is to get something
+ identifiable to show up in the root scroll frame's content description,
+ so we can check it for layerization. -->
+<html id="outer3">
+ <head>
+ <link rel="stylesheet" type="text/css" href="helper_subframe_style.css"/>
+ </head>
+ <body>
+ <div id="inner3" class="inner-frame">
+ <div class="inner-content"></div>
+ </div>
+ </body>
+</html>
diff --git a/gfx/layers/apz/test/mochitest/helper_iframe2.html b/gfx/layers/apz/test/mochitest/helper_iframe2.html
new file mode 100644
index 000000000..fee3883e9
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/helper_iframe2.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<!-- The purpose of the 'id' on the HTML element is to get something
+ identifiable to show up in the root scroll frame's content description,
+ so we can check it for layerization. -->
+<html id="outer4">
+ <head>
+ <link rel="stylesheet" type="text/css" href="helper_subframe_style.css"/>
+ </head>
+ <body>
+ <div id="inner4" class="inner-frame">
+ <div class="inner-content"></div>
+ </div>
+ </body>
+</html>
diff --git a/gfx/layers/apz/test/mochitest/helper_iframe_pan.html b/gfx/layers/apz/test/mochitest/helper_iframe_pan.html
new file mode 100644
index 000000000..47213f33a
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/helper_iframe_pan.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width; initial-scale=1.0">
+ <title>Sanity panning test for scrollable div</title>
+ <script type="application/javascript" src="apz_test_native_event_utils.js"></script>
+ <script type="application/javascript" src="apz_test_utils.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
+ <script type="application/javascript">
+
+function scrollOuter() {
+ var outer = document.getElementById('outer');
+ var transformEnd = function() {
+ SpecialPowers.Services.obs.removeObserver(transformEnd, "APZ:TransformEnd", false);
+ dump("Transform complete; flushing repaints...\n");
+ flushApzRepaints(checkScroll, outer.contentWindow);
+ };
+ SpecialPowers.Services.obs.addObserver(transformEnd, "APZ:TransformEnd", false);
+
+ synthesizeNativeTouchDrag(outer.contentDocument.body, 10, 100, 0, -(50 + TOUCH_SLOP));
+ dump("Finished native drag, waiting for transform-end observer...\n");
+}
+
+function checkScroll() {
+ var outerScroll = document.getElementById('outer').contentWindow.scrollY;
+ is(outerScroll, 50, "check that the iframe scrolled");
+ subtestDone();
+}
+
+waitUntilApzStable().then(scrollOuter);
+
+ </script>
+</head>
+<body>
+ <iframe id="outer" style="height: 250px; border: solid 1px black" src="data:text/html,<body style='height:5000px'>"></iframe>
+ <div style="height: 5000px; background-color: lightgreen;">
+ This div makes the top-level page scrollable.
+ </div>
+</body>
+</html>
diff --git a/gfx/layers/apz/test/mochitest/helper_long_tap.html b/gfx/layers/apz/test/mochitest/helper_long_tap.html
new file mode 100644
index 000000000..604d03d64
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/helper_long_tap.html
@@ -0,0 +1,81 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width; initial-scale=1.0">
+ <title>Ensure we get a touch-cancel after a contextmenu comes up</title>
+ <script type="application/javascript" src="apz_test_native_event_utils.js"></script>
+ <script type="application/javascript" src="apz_test_utils.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
+ <script type="application/javascript">
+
+function longPressLink() {
+ synthesizeNativeTouch(document.getElementById('b'), 5, 5, SpecialPowers.DOMWindowUtils.TOUCH_CONTACT, function() {
+ dump("Finished synthesizing touch-start, waiting for events...\n");
+ });
+}
+
+var eventsFired = 0;
+function recordEvent(e) {
+ if (getPlatform() == "windows") {
+ // On Windows we get a mouselongtap event once the long-tap has been detected
+ // by APZ, and that's what we use as the trigger to lift the finger. That then
+ // triggers the contextmenu. This matches the platform convention.
+ switch (eventsFired) {
+ case 0: is(e.type, 'touchstart', 'Got a touchstart'); break;
+ case 1:
+ is(e.type, 'mouselongtap', 'Got a mouselongtap');
+ synthesizeNativeTouch(document.getElementById('b'), 5, 5, SpecialPowers.DOMWindowUtils.TOUCH_REMOVE);
+ break;
+ case 2: is(e.type, 'touchend', 'Got a touchend'); break;
+ case 3: is(e.type, 'contextmenu', 'Got a contextmenu'); e.preventDefault(); break;
+ default: ok(false, 'Got an unexpected event of type ' + e.type); break;
+ }
+ eventsFired++;
+
+ if (eventsFired == 4) {
+ dump("Finished waiting for events, doing an APZ flush to see if any more unexpected events come through...\n");
+ flushApzRepaints(function() {
+ dump("Done APZ flush, ending test...\n");
+ subtestDone();
+ });
+ }
+ } else {
+ // On non-Windows platforms we get a contextmenu event once the long-tap has
+ // been detected. Since we prevent-default that, we don't get a mouselongtap
+ // event at all, and instead get a touchcancel.
+ switch (eventsFired) {
+ case 0: is(e.type, 'touchstart', 'Got a touchstart'); break;
+ case 1: is(e.type, 'contextmenu', 'Got a contextmenu'); e.preventDefault(); break;
+ case 2: is(e.type, 'touchcancel', 'Got a touchcancel'); break;
+ default: ok(false, 'Got an unexpected event of type ' + e.type); break;
+ }
+ eventsFired++;
+
+ if (eventsFired == 3) {
+ synthesizeNativeTouch(document.getElementById('b'), 5, 5, SpecialPowers.DOMWindowUtils.TOUCH_REMOVE, function() {
+ dump("Finished synthesizing touch-end, doing an APZ flush to see if any more unexpected events come through...\n");
+ flushApzRepaints(function() {
+ dump("Done APZ flush, ending test...\n");
+ subtestDone();
+ });
+ });
+ }
+ }
+}
+
+window.addEventListener('touchstart', recordEvent, { passive: true, capture: true });
+window.addEventListener('touchend', recordEvent, { passive: true, capture: true });
+window.addEventListener('touchcancel', recordEvent, true);
+window.addEventListener('contextmenu', recordEvent, true);
+SpecialPowers.addChromeEventListener('mouselongtap', recordEvent, true);
+
+waitUntilApzStable()
+.then(longPressLink);
+
+ </script>
+</head>
+<body>
+ <a id="b" href="#">Link to nowhere</a>
+</body>
+</html>
diff --git a/gfx/layers/apz/test/mochitest/helper_scroll_inactive_perspective.html b/gfx/layers/apz/test/mochitest/helper_scroll_inactive_perspective.html
new file mode 100644
index 000000000..da866c1ce
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/helper_scroll_inactive_perspective.html
@@ -0,0 +1,46 @@
+<head>
+ <meta name="viewport" content="width=device-width; initial-scale=1.0">
+ <title>Wheel-scrolling over inactive subframe with perspective</title>
+ <script type="application/javascript" src="apz_test_native_event_utils.js"></script>
+ <script type="application/javascript" src="apz_test_utils.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
+ <script type="application/javascript">
+
+function* test(testDriver) {
+ var subframe = document.getElementById('scroll');
+
+ // scroll over the middle of the subframe, to make sure it scrolls,
+ // not the page
+ var scrollPos = subframe.scrollTop;
+ yield moveMouseAndScrollWheelOver(subframe, 100, 100, testDriver);
+ dump("after scroll, subframe.scrollTop = " + subframe.scrollTop + "\n");
+ ok(subframe.scrollTop > scrollPos, "subframe scrolled after wheeling over it");
+}
+
+waitUntilApzStable()
+.then(runContinuation(test))
+.then(subtestDone);
+
+ </script>
+ <style>
+ #scroll {
+ width: 200px;
+ height: 200px;
+ overflow: scroll;
+ perspective: 400px;
+ }
+ #scrolled {
+ width: 200px;
+ height: 1000px; /* so the subframe has room to scroll */
+ background: linear-gradient(red, blue); /* so you can see it scroll */
+ transform: translateZ(0px); /* so the perspective makes it to the display list */
+ }
+ </style>
+</head>
+<body>
+ <div id="scroll">
+ <div id="scrolled"></div>
+ </div>
+ <div style="height: 5000px;"></div><!-- So the page is scrollable as well -->
+</body>
+</head>
diff --git a/gfx/layers/apz/test/mochitest/helper_scroll_inactive_zindex.html b/gfx/layers/apz/test/mochitest/helper_scroll_inactive_zindex.html
new file mode 100644
index 000000000..763aaf92b
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/helper_scroll_inactive_zindex.html
@@ -0,0 +1,47 @@
+<head>
+ <meta name="viewport" content="width=device-width; initial-scale=1.0">
+ <title>Wheel-scrolling over inactive subframe with z-index</title>
+ <script type="application/javascript" src="apz_test_native_event_utils.js"></script>
+ <script type="application/javascript" src="apz_test_utils.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
+ <script type="application/javascript">
+
+function* test(testDriver) {
+ var subframe = document.getElementById('scroll');
+
+ // scroll over the middle of the subframe, and make sure that it scrolls,
+ // not the page
+ var scrollPos = subframe.scrollTop;
+ yield moveMouseAndScrollWheelOver(subframe, 100, 100, testDriver);
+ dump("after scroll, subframe.scrollTop = " + subframe.scrollTop + "\n");
+ ok(subframe.scrollTop > scrollPos, "subframe scrolled after wheeling over it");
+}
+
+waitUntilApzStable()
+.then(runContinuation(test))
+.then(subtestDone);
+
+ </script>
+ <style>
+ #scroll {
+ width: 200px;
+ height: 200px;
+ overflow: scroll;
+ }
+ #scrolled {
+ width: 200px;
+ height: 1000px; /* so the subframe has room to scroll */
+ z-index: 2;
+ background: linear-gradient(red, blue); /* so you can see it scroll */
+ transform: translateZ(0px); /* to force active layers */
+ will-change: transform; /* to force active layers */
+ }
+ </style>
+</head>
+<body>
+ <div id="scroll">
+ <div id="scrolled"></div>
+ </div>
+ <div style="height: 5000px;"></div><!-- So the page is scrollable as well -->
+</body>
+</head>
diff --git a/gfx/layers/apz/test/mochitest/helper_scroll_on_position_fixed.html b/gfx/layers/apz/test/mochitest/helper_scroll_on_position_fixed.html
new file mode 100644
index 000000000..b9d187faf
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/helper_scroll_on_position_fixed.html
@@ -0,0 +1,62 @@
+<head>
+ <meta name="viewport" content="width=device-width; initial-scale=1.0">
+ <title>Wheel-scrolling over position:fixed and position:sticky elements, in the top-level document as well as iframes</title>
+ <script type="application/javascript" src="apz_test_native_event_utils.js"></script>
+ <script type="application/javascript" src="apz_test_utils.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
+ <script type="application/javascript">
+
+function* test(testDriver) {
+ var iframeWin = document.getElementById('iframe').contentWindow;
+
+ // scroll over the middle of the iframe's position:sticky element, check
+ // that it scrolls the iframe
+ var scrollPos = iframeWin.scrollY;
+ yield moveMouseAndScrollWheelOver(iframeWin.document.body, 50, 150, testDriver);
+ ok(iframeWin.scrollY > scrollPos, "iframe scrolled after wheeling over the position:sticky element");
+
+ // same, but using the iframe's position:fixed element
+ scrollPos = iframeWin.scrollY;
+ yield moveMouseAndScrollWheelOver(iframeWin.document.body, 250, 150, testDriver);
+ ok(iframeWin.scrollY > scrollPos, "iframe scrolled after wheeling over the position:fixed element");
+
+ // same, but scrolling the scrollable frame *inside* the position:fixed item
+ var fpos = document.getElementById('fpos_scrollable');
+ scrollPos = fpos.scrollTop;
+ yield moveMouseAndScrollWheelOver(fpos, 50, 150, testDriver);
+ ok(fpos.scrollTop > scrollPos, "scrollable item inside fixed-pos element scrolled");
+ // wait for it to layerize fully and then try again
+ yield waitForAllPaints(function() {
+ flushApzRepaints(testDriver);
+ });
+ scrollPos = fpos.scrollTop;
+ yield moveMouseAndScrollWheelOver(fpos, 50, 150, testDriver);
+ ok(fpos.scrollTop > scrollPos, "scrollable item inside fixed-pos element scrolled after layerization");
+
+ // same, but using the top-level window's position:sticky element
+ scrollPos = window.scrollY;
+ yield moveMouseAndScrollWheelOver(document.body, 50, 150, testDriver);
+ ok(window.scrollY > scrollPos, "top-level document scrolled after wheeling over the position:sticky element");
+
+ // same, but using the top-level window's position:fixed element
+ scrollPos = window.scrollY;
+ yield moveMouseAndScrollWheelOver(document.body, 250, 150, testDriver);
+ ok(window.scrollY > scrollPos, "top-level document scrolled after wheeling over the position:fixed element");
+}
+
+waitUntilApzStable()
+.then(runContinuation(test))
+.then(subtestDone);
+
+ </script>
+</head>
+<body style="height:5000px; margin:0">
+ <div style="position:sticky; width: 100px; height: 300px; top: 0; background-color:red">sticky</div>
+ <div style="position:fixed; width: 100px; height: 300px; top: 0; left: 200px; background-color: green">fixed</div>
+ <iframe id='iframe' width="300" height="400" src="data:text/html,<body style='height:5000px; margin:0'><div style='position:sticky; width:100px; height:300px; top: 0; background-color:red'>sticky</div><div style='position:fixed; right:0; top: 0; width:100px; height:300px; background-color:green'>fixed</div>"></iframe>
+
+ <div id="fpos_scrollable" style="position:fixed; width: 100px; height: 300px; top: 0; left: 400px; background-color: red; overflow:scroll">
+ <div style="background-color: blue; height: 1000px; margin: 3px">scrollable content inside a fixed-pos item</div>
+ </div>
+</body>
+</head>
diff --git a/gfx/layers/apz/test/mochitest/helper_scrollto_tap.html b/gfx/layers/apz/test/mochitest/helper_scrollto_tap.html
new file mode 100644
index 000000000..fc444f2b7
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/helper_scrollto_tap.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width; initial-scale=1.0">
+ <title>Sanity touch-tapping test</title>
+ <script type="application/javascript" src="apz_test_native_event_utils.js"></script>
+ <script type="application/javascript" src="apz_test_utils.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
+ <script type="application/javascript">
+
+function startTest() {
+ if (window.scrollY == 0) {
+ // the scrollframe is not yet marked as APZ-scrollable. Mark it so and
+ // start over.
+ window.scrollTo(0, 1);
+ waitForApzFlushedRepaints(startTest);
+ return;
+ }
+
+ // This is a scroll by 20px that should use paint-skipping if possible.
+ // If paint-skipping is enabled, this should not trigger a paint, but go
+ // directly to the compositor using an empty transaction. We check for this
+ // by ensuring the document element did not get painted.
+ var utils = window.opener.SpecialPowers.getDOMWindowUtils(window);
+ var elem = document.documentElement;
+ var skipping = location.search == '?true';
+ utils.checkAndClearPaintedState(elem);
+ window.scrollTo(0, 20);
+ waitForAllPaints(function() {
+ if (skipping) {
+ is(utils.checkAndClearPaintedState(elem), false, "Document element didn't get painted");
+ }
+ // After that's done, we click on the button to make sure the
+ // skipped-paint codepath still has working APZ event transformations.
+ clickButton();
+ });
+}
+
+function clickButton() {
+ document.addEventListener('click', clicked, false);
+
+ synthesizeNativeTap(document.getElementById('b'), 5, 5, function() {
+ dump("Finished synthesizing tap, waiting for button to be clicked...\n");
+ });
+}
+
+function clicked(e) {
+ is(e.target, document.getElementById('b'), "Clicked on button, yay! (at " + e.clientX + "," + e.clientY + ")");
+ subtestDone();
+}
+
+waitUntilApzStable().then(startTest);
+
+ </script>
+</head>
+<body style="height: 5000px">
+ <div style="height: 50px">spacer</div>
+ <button id="b" style="width: 10px; height: 10px"></button>
+</body>
+</html>
diff --git a/gfx/layers/apz/test/mochitest/helper_subframe_style.css b/gfx/layers/apz/test/mochitest/helper_subframe_style.css
new file mode 100644
index 000000000..5af964080
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/helper_subframe_style.css
@@ -0,0 +1,15 @@
+body {
+ height: 500px;
+}
+
+.inner-frame {
+ margin-top: 50px; /* this should be at least 30px */
+ height: 200%;
+ width: 75%;
+ overflow: scroll;
+}
+.inner-content {
+ height: 200%;
+ width: 200%;
+ background: repeating-linear-gradient(#EEE, #EEE 100px, #DDD 100px, #DDD 200px);
+}
diff --git a/gfx/layers/apz/test/mochitest/helper_tall.html b/gfx/layers/apz/test/mochitest/helper_tall.html
new file mode 100644
index 000000000..7fde795fd
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/helper_tall.html
@@ -0,0 +1,504 @@
+<html id="tall_html">
+<body>
+This is a tall page<br/>
+1<br/>
+2<br/>
+3<br/>
+4<br/>
+5<br/>
+6<br/>
+7<br/>
+8<br/>
+9<br/>
+10<br/>
+11<br/>
+12<br/>
+13<br/>
+14<br/>
+15<br/>
+16<br/>
+17<br/>
+18<br/>
+19<br/>
+20<br/>
+21<br/>
+22<br/>
+23<br/>
+24<br/>
+25<br/>
+26<br/>
+27<br/>
+28<br/>
+29<br/>
+30<br/>
+31<br/>
+32<br/>
+33<br/>
+34<br/>
+35<br/>
+36<br/>
+37<br/>
+38<br/>
+39<br/>
+40<br/>
+41<br/>
+42<br/>
+43<br/>
+44<br/>
+45<br/>
+46<br/>
+47<br/>
+48<br/>
+49<br/>
+50<br/>
+51<br/>
+52<br/>
+53<br/>
+54<br/>
+55<br/>
+56<br/>
+57<br/>
+58<br/>
+59<br/>
+60<br/>
+61<br/>
+62<br/>
+63<br/>
+64<br/>
+65<br/>
+66<br/>
+67<br/>
+68<br/>
+69<br/>
+70<br/>
+71<br/>
+72<br/>
+73<br/>
+74<br/>
+75<br/>
+76<br/>
+77<br/>
+78<br/>
+79<br/>
+80<br/>
+81<br/>
+82<br/>
+83<br/>
+84<br/>
+85<br/>
+86<br/>
+87<br/>
+88<br/>
+89<br/>
+90<br/>
+91<br/>
+92<br/>
+93<br/>
+94<br/>
+95<br/>
+96<br/>
+97<br/>
+98<br/>
+99<br/>
+100<br/>
+101<br/>
+102<br/>
+103<br/>
+104<br/>
+105<br/>
+106<br/>
+107<br/>
+108<br/>
+109<br/>
+110<br/>
+111<br/>
+112<br/>
+113<br/>
+114<br/>
+115<br/>
+116<br/>
+117<br/>
+118<br/>
+119<br/>
+120<br/>
+121<br/>
+122<br/>
+123<br/>
+124<br/>
+125<br/>
+126<br/>
+127<br/>
+128<br/>
+129<br/>
+130<br/>
+131<br/>
+132<br/>
+133<br/>
+134<br/>
+135<br/>
+136<br/>
+137<br/>
+138<br/>
+139<br/>
+140<br/>
+141<br/>
+142<br/>
+143<br/>
+144<br/>
+145<br/>
+146<br/>
+147<br/>
+148<br/>
+149<br/>
+150<br/>
+151<br/>
+152<br/>
+153<br/>
+154<br/>
+155<br/>
+156<br/>
+157<br/>
+158<br/>
+159<br/>
+160<br/>
+161<br/>
+162<br/>
+163<br/>
+164<br/>
+165<br/>
+166<br/>
+167<br/>
+168<br/>
+169<br/>
+170<br/>
+171<br/>
+172<br/>
+173<br/>
+174<br/>
+175<br/>
+176<br/>
+177<br/>
+178<br/>
+179<br/>
+180<br/>
+181<br/>
+182<br/>
+183<br/>
+184<br/>
+185<br/>
+186<br/>
+187<br/>
+188<br/>
+189<br/>
+190<br/>
+191<br/>
+192<br/>
+193<br/>
+194<br/>
+195<br/>
+196<br/>
+197<br/>
+198<br/>
+199<br/>
+200<br/>
+201<br/>
+202<br/>
+203<br/>
+204<br/>
+205<br/>
+206<br/>
+207<br/>
+208<br/>
+209<br/>
+210<br/>
+211<br/>
+212<br/>
+213<br/>
+214<br/>
+215<br/>
+216<br/>
+217<br/>
+218<br/>
+219<br/>
+220<br/>
+221<br/>
+222<br/>
+223<br/>
+224<br/>
+225<br/>
+226<br/>
+227<br/>
+228<br/>
+229<br/>
+230<br/>
+231<br/>
+232<br/>
+233<br/>
+234<br/>
+235<br/>
+236<br/>
+237<br/>
+238<br/>
+239<br/>
+240<br/>
+241<br/>
+242<br/>
+243<br/>
+244<br/>
+245<br/>
+246<br/>
+247<br/>
+248<br/>
+249<br/>
+250<br/>
+251<br/>
+252<br/>
+253<br/>
+254<br/>
+255<br/>
+256<br/>
+257<br/>
+258<br/>
+259<br/>
+260<br/>
+261<br/>
+262<br/>
+263<br/>
+264<br/>
+265<br/>
+266<br/>
+267<br/>
+268<br/>
+269<br/>
+270<br/>
+271<br/>
+272<br/>
+273<br/>
+274<br/>
+275<br/>
+276<br/>
+277<br/>
+278<br/>
+279<br/>
+280<br/>
+281<br/>
+282<br/>
+283<br/>
+284<br/>
+285<br/>
+286<br/>
+287<br/>
+288<br/>
+289<br/>
+290<br/>
+291<br/>
+292<br/>
+293<br/>
+294<br/>
+295<br/>
+296<br/>
+297<br/>
+298<br/>
+299<br/>
+300<br/>
+301<br/>
+302<br/>
+303<br/>
+304<br/>
+305<br/>
+306<br/>
+307<br/>
+308<br/>
+309<br/>
+310<br/>
+311<br/>
+312<br/>
+313<br/>
+314<br/>
+315<br/>
+316<br/>
+317<br/>
+318<br/>
+319<br/>
+320<br/>
+321<br/>
+322<br/>
+323<br/>
+324<br/>
+325<br/>
+326<br/>
+327<br/>
+328<br/>
+329<br/>
+330<br/>
+331<br/>
+332<br/>
+333<br/>
+334<br/>
+335<br/>
+336<br/>
+337<br/>
+338<br/>
+339<br/>
+340<br/>
+341<br/>
+342<br/>
+343<br/>
+344<br/>
+345<br/>
+346<br/>
+347<br/>
+348<br/>
+349<br/>
+350<br/>
+351<br/>
+352<br/>
+353<br/>
+354<br/>
+355<br/>
+356<br/>
+357<br/>
+358<br/>
+359<br/>
+360<br/>
+361<br/>
+362<br/>
+363<br/>
+364<br/>
+365<br/>
+366<br/>
+367<br/>
+368<br/>
+369<br/>
+370<br/>
+371<br/>
+372<br/>
+373<br/>
+374<br/>
+375<br/>
+376<br/>
+377<br/>
+378<br/>
+379<br/>
+380<br/>
+381<br/>
+382<br/>
+383<br/>
+384<br/>
+385<br/>
+386<br/>
+387<br/>
+388<br/>
+389<br/>
+390<br/>
+391<br/>
+392<br/>
+393<br/>
+394<br/>
+395<br/>
+396<br/>
+397<br/>
+398<br/>
+399<br/>
+400<br/>
+401<br/>
+402<br/>
+403<br/>
+404<br/>
+405<br/>
+406<br/>
+407<br/>
+408<br/>
+409<br/>
+410<br/>
+411<br/>
+412<br/>
+413<br/>
+414<br/>
+415<br/>
+416<br/>
+417<br/>
+418<br/>
+419<br/>
+420<br/>
+421<br/>
+422<br/>
+423<br/>
+424<br/>
+425<br/>
+426<br/>
+427<br/>
+428<br/>
+429<br/>
+430<br/>
+431<br/>
+432<br/>
+433<br/>
+434<br/>
+435<br/>
+436<br/>
+437<br/>
+438<br/>
+439<br/>
+440<br/>
+441<br/>
+442<br/>
+443<br/>
+444<br/>
+445<br/>
+446<br/>
+447<br/>
+448<br/>
+449<br/>
+450<br/>
+451<br/>
+452<br/>
+453<br/>
+454<br/>
+455<br/>
+456<br/>
+457<br/>
+458<br/>
+459<br/>
+460<br/>
+461<br/>
+462<br/>
+463<br/>
+464<br/>
+465<br/>
+466<br/>
+467<br/>
+468<br/>
+469<br/>
+470<br/>
+471<br/>
+472<br/>
+473<br/>
+474<br/>
+475<br/>
+476<br/>
+477<br/>
+478<br/>
+479<br/>
+480<br/>
+481<br/>
+482<br/>
+483<br/>
+484<br/>
+485<br/>
+486<br/>
+487<br/>
+488<br/>
+489<br/>
+490<br/>
+491<br/>
+492<br/>
+493<br/>
+494<br/>
+495<br/>
+496<br/>
+497<br/>
+498<br/>
+499<br/>
+</body>
+</html>
diff --git a/gfx/layers/apz/test/mochitest/helper_tap.html b/gfx/layers/apz/test/mochitest/helper_tap.html
new file mode 100644
index 000000000..6fde9387d
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/helper_tap.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width; initial-scale=1.0">
+ <title>Sanity touch-tapping test</title>
+ <script type="application/javascript" src="apz_test_native_event_utils.js"></script>
+ <script type="application/javascript" src="apz_test_utils.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
+ <script type="application/javascript">
+
+function clickButton() {
+ document.addEventListener('click', clicked, false);
+
+ synthesizeNativeTap(document.getElementById('b'), 5, 5, function() {
+ dump("Finished synthesizing tap, waiting for button to be clicked...\n");
+ });
+}
+
+function clicked(e) {
+ is(e.target, document.getElementById('b'), "Clicked on button, yay! (at " + e.clientX + "," + e.clientY + ")");
+ subtestDone();
+}
+
+waitUntilApzStable().then(clickButton);
+
+ </script>
+</head>
+<body>
+ <button id="b" style="width: 10px; height: 10px"></button>
+</body>
+</html>
diff --git a/gfx/layers/apz/test/mochitest/helper_tap_fullzoom.html b/gfx/layers/apz/test/mochitest/helper_tap_fullzoom.html
new file mode 100644
index 000000000..494363b9c
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/helper_tap_fullzoom.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width; initial-scale=1.0">
+ <title>Sanity touch-tapping test with fullzoom</title>
+ <script type="application/javascript" src="apz_test_native_event_utils.js"></script>
+ <script type="application/javascript" src="apz_test_utils.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
+ <script type="application/javascript">
+
+function clickButton() {
+ document.addEventListener('click', clicked, false);
+
+ synthesizeNativeTap(document.getElementById('b'), 5, 5, function() {
+ dump("Finished synthesizing tap, waiting for button to be clicked...\n");
+ });
+}
+
+function clicked(e) {
+ is(e.target, document.getElementById('b'), "Clicked on button, yay! (at " + e.clientX + "," + e.clientY + ")");
+ subtestDone();
+}
+
+SpecialPowers.setFullZoom(window, 2.0);
+waitUntilApzStable().then(clickButton);
+
+ </script>
+</head>
+<body>
+ <button id="b" style="width: 10px; height: 10px; position: relative; top: 100px"></button>
+</body>
+</html>
diff --git a/gfx/layers/apz/test/mochitest/helper_tap_passive.html b/gfx/layers/apz/test/mochitest/helper_tap_passive.html
new file mode 100644
index 000000000..dc3d85ed2
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/helper_tap_passive.html
@@ -0,0 +1,64 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width; initial-scale=1.0">
+ <title>Ensure APZ doesn't wait for passive listeners</title>
+ <script type="application/javascript" src="apz_test_native_event_utils.js"></script>
+ <script type="application/javascript" src="apz_test_utils.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
+ <script type="application/javascript">
+
+var touchdownTime;
+
+function longPressLink() {
+ synthesizeNativeTouch(document.getElementById('b'), 5, 5, SpecialPowers.DOMWindowUtils.TOUCH_CONTACT, function() {
+ dump("Finished synthesizing touch-start, waiting for events...\n");
+ });
+}
+
+var touchstartReceived = false;
+function recordEvent(e) {
+ if (!touchstartReceived) {
+ touchstartReceived = true;
+ is(e.type, 'touchstart', 'Got a touchstart');
+ e.preventDefault(); // should be a no-op because it's a passive listener
+ return;
+ }
+
+ // If APZ decides to wait for the content response on a particular input block,
+ // it needs to wait until both the touchstart and touchmove event are handled
+ // by the main thread. In this case there is no touchmove at all, so APZ would
+ // end up waiting indefinitely and time out the test. The fact that we get this
+ // contextmenu event (mouselongtap on Windows) at all means that APZ decided
+ // not to wait for the content response, which is the desired behaviour, since
+ // the touchstart listener was registered as a passive listener.
+ if (getPlatform() == "windows") {
+ is(e.type, 'mouselongtap', 'Got a mouselongtap');
+ } else {
+ is(e.type, 'contextmenu', 'Got a contextmenu');
+ }
+ e.preventDefault();
+
+ synthesizeNativeTouch(document.getElementById('b'), 5, 5, SpecialPowers.DOMWindowUtils.TOUCH_REMOVE, function() {
+ dump("Finished synthesizing touch-end to clear state; finishing test...\n");
+ subtestDone();
+ });
+}
+
+window.addEventListener('touchstart', recordEvent, { passive: true, capture: true });
+if (getPlatform() == "windows") {
+ SpecialPowers.addChromeEventListener('mouselongtap', recordEvent, true);
+} else {
+ window.addEventListener('contextmenu', recordEvent, true);
+}
+
+waitUntilApzStable()
+.then(longPressLink);
+
+ </script>
+</head>
+<body>
+ <a id="b" href="#">Link to nowhere</a>
+</body>
+</html>
diff --git a/gfx/layers/apz/test/mochitest/helper_touch_action.html b/gfx/layers/apz/test/mochitest/helper_touch_action.html
new file mode 100644
index 000000000..4495dc76e
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/helper_touch_action.html
@@ -0,0 +1,115 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width; initial-scale=1.0">
+ <title>Sanity touch-action test</title>
+ <script type="application/javascript" src="apz_test_native_event_utils.js"></script>
+ <script type="application/javascript" src="apz_test_utils.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
+ <script type="application/javascript">
+
+function checkScroll(x, y, desc) {
+ is(window.scrollX, x, desc + " - x axis");
+ is(window.scrollY, y, desc + " - y axis");
+}
+
+function* test(testDriver) {
+ var target = document.getElementById('target');
+
+ document.body.addEventListener('touchend', testDriver, { passive: true });
+
+ // drag the page up to scroll down by 50px
+ yield ok(synthesizeNativeTouchDrag(target, 10, 100, 0, -(50 + TOUCH_SLOP)),
+ "Synthesized native vertical drag (1), waiting for touch-end event...");
+ yield flushApzRepaints(testDriver);
+ checkScroll(0, 50, "After first vertical drag, with pan-y" );
+
+ // switch style to pan-x
+ document.body.style.touchAction = 'pan-x';
+ ok(true, "Waiting for pan-x to propagate...");
+ yield waitForAllPaintsFlushed(function() {
+ flushApzRepaints(testDriver);
+ });
+
+ // drag the page up to scroll down by 50px, but it won't happen because pan-x
+ yield ok(synthesizeNativeTouchDrag(target, 10, 100, 0, -(50 + TOUCH_SLOP)),
+ "Synthesized native vertical drag (2), waiting for touch-end event...");
+ yield flushApzRepaints(testDriver);
+ checkScroll(0, 50, "After second vertical drag, with pan-x");
+
+ // drag the page left to scroll right by 50px
+ yield ok(synthesizeNativeTouchDrag(target, 100, 10, -(50 + TOUCH_SLOP), 0),
+ "Synthesized horizontal drag (1), waiting for touch-end event...");
+ yield flushApzRepaints(testDriver);
+ checkScroll(50, 50, "After first horizontal drag, with pan-x");
+
+ // drag the page diagonally right/down to scroll up/left by 40px each axis;
+ // only the x-axis will actually scroll because pan-x
+ yield ok(synthesizeNativeTouchDrag(target, 10, 10, (40 + TOUCH_SLOP), (40 + TOUCH_SLOP)),
+ "Synthesized diagonal drag (1), waiting for touch-end event...");
+ yield flushApzRepaints(testDriver);
+ checkScroll(10, 50, "After first diagonal drag, with pan-x");
+
+ // switch style back to pan-y
+ document.body.style.touchAction = 'pan-y';
+ ok(true, "Waiting for pan-y to propagate...");
+ yield waitForAllPaintsFlushed(function() {
+ flushApzRepaints(testDriver);
+ });
+
+ // drag the page diagonally right/down to scroll up/left by 40px each axis;
+ // only the y-axis will actually scroll because pan-y
+ yield ok(synthesizeNativeTouchDrag(target, 10, 10, (40 + TOUCH_SLOP), (40 + TOUCH_SLOP)),
+ "Synthesized diagonal drag (2), waiting for touch-end event...");
+ yield flushApzRepaints(testDriver);
+ checkScroll(10, 10, "After second diagonal drag, with pan-y");
+
+ // switch style to none
+ document.body.style.touchAction = 'none';
+ ok(true, "Waiting for none to propagate...");
+ yield waitForAllPaintsFlushed(function() {
+ flushApzRepaints(testDriver);
+ });
+
+ // drag the page diagonally up/left to scroll down/right by 40px each axis;
+ // neither will scroll because of touch-action
+ yield ok(synthesizeNativeTouchDrag(target, 100, 100, -(40 + TOUCH_SLOP), -(40 + TOUCH_SLOP)),
+ "Synthesized diagonal drag (3), waiting for touch-end event...");
+ yield flushApzRepaints(testDriver);
+ checkScroll(10, 10, "After third diagonal drag, with none");
+
+ document.body.style.touchAction = 'manipulation';
+ ok(true, "Waiting for manipulation to propagate...");
+ yield waitForAllPaintsFlushed(function() {
+ flushApzRepaints(testDriver);
+ });
+
+ // drag the page diagonally up/left to scroll down/right by 40px each axis;
+ // both will scroll because of touch-action
+ yield ok(synthesizeNativeTouchDrag(target, 100, 100, -(40 + TOUCH_SLOP), -(40 + TOUCH_SLOP)),
+ "Synthesized diagonal drag (4), waiting for touch-end event...");
+ yield flushApzRepaints(testDriver);
+ checkScroll(50, 50, "After fourth diagonal drag, with manipulation");
+}
+
+waitUntilApzStable()
+.then(runContinuation(test))
+.then(subtestDone);
+
+ </script>
+</head>
+<body style="touch-action: pan-y">
+ <div style="width: 5000px; height: 5000px; background-color: lightgreen;">
+ This div makes the page scrollable on both axes.<br>
+ This is the second line of text.<br>
+ This is the third line of text.<br>
+ This is the fourth line of text.
+ </div>
+ <!-- This fixed-position div remains in the same place relative to the browser chrome, so we
+ can use it as a targeting device for synthetic touch events. The body will move around
+ as we scroll, so we'd have to be constantly adjusting the synthetic drag coordinates
+ if we used that as the target element. -->
+ <div style="position:fixed; left: 10px; top: 10px; width: 1px; height: 1px" id="target"></div>
+</body>
+</html>
diff --git a/gfx/layers/apz/test/mochitest/helper_touch_action_complex.html b/gfx/layers/apz/test/mochitest/helper_touch_action_complex.html
new file mode 100644
index 000000000..11d6e66e1
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/helper_touch_action_complex.html
@@ -0,0 +1,143 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width; initial-scale=1.0">
+ <title>Complex touch-action test</title>
+ <script type="application/javascript" src="apz_test_native_event_utils.js"></script>
+ <script type="application/javascript" src="apz_test_utils.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
+ <script type="application/javascript">
+
+function checkScroll(target, x, y, desc) {
+ is(target.scrollLeft, x, desc + " - x axis");
+ is(target.scrollTop, y, desc + " - y axis");
+}
+
+function resetConfiguration(config, testDriver) {
+ // Cycle through all the configuration_X elements, setting them to display:none
+ // except for when X == config, in which case set it to display:block
+ var i = 0;
+ while (true) {
+ i++;
+ var element = document.getElementById('configuration_' + i);
+ if (element == null) {
+ if (i <= config) {
+ ok(false, "The configuration requested was not encountered!");
+ }
+ break;
+ }
+
+ if (i == config) {
+ element.style.display = 'block';
+ } else {
+ element.style.display = 'none';
+ }
+ }
+
+ // Also reset the scroll position on the scrollframe
+ var s = document.getElementById('scrollframe');
+ s.scrollLeft = 0;
+ s.scrollTop = 0;
+
+ return waitForAllPaints(function() {
+ flushApzRepaints(testDriver);
+ });
+}
+
+function* test(testDriver) {
+ var scrollframe = document.getElementById('scrollframe');
+
+ document.body.addEventListener('touchend', testDriver, { passive: true });
+
+ // Helper function for the tests below.
+ // Touch-pan configuration |configuration| towards scroll offset (dx, dy) with
+ // the pan touching down at (x, y). Check that the final scroll offset is
+ // (ex, ey). |desc| is some description string.
+ function* scrollAndCheck(configuration, x, y, dx, dy, ex, ey, desc) {
+ // Start with a clean slate
+ yield resetConfiguration(configuration, testDriver);
+ // Figure out the panning deltas
+ if (dx != 0) {
+ dx = -(dx + TOUCH_SLOP);
+ }
+ if (dy != 0) {
+ dy = -(dy + TOUCH_SLOP);
+ }
+ // Do the pan
+ yield ok(synthesizeNativeTouchDrag(scrollframe, x, y, dx, dy),
+ "Synthesized drag of (" + dx + ", " + dy + ") on configuration " + configuration);
+ yield waitForAllPaints(function() {
+ flushApzRepaints(testDriver);
+ });
+ // Check for expected scroll position
+ checkScroll(scrollframe, ex, ey, 'configuration ' + configuration + ' ' + desc);
+ }
+
+ // Test configuration_1, which contains two sibling elements that are
+ // overlapping. The touch-action from the second sibling (which is on top)
+ // should be used for the overlapping area.
+ yield* scrollAndCheck(1, 25, 75, 20, 0, 20, 0, "first element horizontal scroll");
+ yield* scrollAndCheck(1, 25, 75, 0, 50, 0, 0, "first element vertical scroll");
+ yield* scrollAndCheck(1, 75, 75, 50, 0, 0, 0, "overlap horizontal scroll");
+ yield* scrollAndCheck(1, 75, 75, 0, 50, 0, 50, "overlap vertical scroll");
+ yield* scrollAndCheck(1, 125, 75, 20, 0, 0, 0, "second element horizontal scroll");
+ yield* scrollAndCheck(1, 125, 75, 0, 50, 0, 50, "second element vertical scroll");
+
+ // Test configuration_2, which contains two overlapping elements with a
+ // parent/child relationship. The parent has pan-x and the child has pan-y,
+ // which means that panning on the parent should work horizontally only, and
+ // on the child no panning should occur at all.
+ yield* scrollAndCheck(2, 125, 125, 50, 50, 0, 0, "child scroll");
+ yield* scrollAndCheck(2, 75, 75, 50, 50, 0, 0, "overlap scroll");
+ yield* scrollAndCheck(2, 25, 75, 0, 50, 0, 0, "parent vertical scroll");
+ yield* scrollAndCheck(2, 75, 25, 50, 0, 50, 0, "parent horizontal scroll");
+
+ // Test configuration_3, which is the same as configuration_2, except the child
+ // has a rotation transform applied. This forces the event regions on the two
+ // elements to be built separately and then get merged.
+ yield* scrollAndCheck(3, 125, 125, 50, 50, 0, 0, "child scroll");
+ yield* scrollAndCheck(3, 75, 75, 50, 50, 0, 0, "overlap scroll");
+ yield* scrollAndCheck(3, 25, 75, 0, 50, 0, 0, "parent vertical scroll");
+ yield* scrollAndCheck(3, 75, 25, 50, 0, 50, 0, "parent horizontal scroll");
+
+ // Test configuration_4 has two elements, one above the other, not overlapping,
+ // and the second element is a child of the first. The parent has pan-x, the
+ // child has pan-y, but that means panning horizontally on the parent should
+ // work and panning in any direction on the child should not do anything.
+ yield* scrollAndCheck(4, 75, 75, 50, 50, 50, 0, "parent diagonal scroll");
+ yield* scrollAndCheck(4, 75, 150, 50, 50, 0, 0, "child diagonal scroll");
+}
+
+waitUntilApzStable()
+.then(runContinuation(test))
+.then(subtestDone);
+
+ </script>
+</head>
+<body>
+ <div id="scrollframe" style="width: 300px; height: 300px; overflow:scroll">
+ <div id="scrolled_content" style="width: 1000px; height: 1000px; background-color: green">
+ </div>
+ <div id="configuration_1" style="display:none; position: relative; top: -1000px">
+ <div style="touch-action: pan-x; width: 100px; height: 100px; background-color: blue"></div>
+ <div style="touch-action: pan-y; width: 100px; height: 100px; position: relative; top: -100px; left: 50px; background-color: yellow"></div>
+ </div>
+ <div id="configuration_2" style="display:none; position: relative; top: -1000px">
+ <div style="touch-action: pan-x; width: 100px; height: 100px; background-color: blue">
+ <div style="touch-action: pan-y; width: 100px; height: 100px; position: relative; top: 50px; left: 50px; background-color: yellow"></div>
+ </div>
+ </div>
+ <div id="configuration_3" style="display:none; position: relative; top: -1000px">
+ <div style="touch-action: pan-x; width: 100px; height: 100px; background-color: blue">
+ <div style="touch-action: pan-y; width: 100px; height: 100px; position: relative; top: 50px; left: 50px; background-color: yellow; transform: rotate(90deg)"></div>
+ </div>
+ </div>
+ <div id="configuration_4" style="display:none; position: relative; top: -1000px">
+ <div style="touch-action: pan-x; width: 100px; height: 100px; background-color: blue">
+ <div style="touch-action: pan-y; width: 100px; height: 100px; position: relative; top: 125px; background-color: yellow"></div>
+ </div>
+ </div>
+ </div>
+</body>
+</html>
diff --git a/gfx/layers/apz/test/mochitest/helper_touch_action_regions.html b/gfx/layers/apz/test/mochitest/helper_touch_action_regions.html
new file mode 100644
index 000000000..cbd4cd61d
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/helper_touch_action_regions.html
@@ -0,0 +1,246 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width; initial-scale=1.0">
+ <title>Test to ensure APZ doesn't always wait for touch-action</title>
+ <script type="application/javascript" src="apz_test_native_event_utils.js"></script>
+ <script type="application/javascript" src="apz_test_utils.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
+ <script type="application/javascript">
+
+function failure(e) {
+ ok(false, "This event listener should not have triggered: " + e.type);
+}
+
+function success(e) {
+ success.triggered = true;
+}
+
+// This helper function provides a way for the child process to synchronously
+// check how many touch events the chrome process main-thread has processed. This
+// function can be called with three values: 'start', 'report', and 'end'.
+// The 'start' invocation sets up the listeners, and should be invoked before
+// the touch events of interest are generated. This should only be called once.
+// This returns true on success, and false on failure.
+// The 'report' invocation can be invoked multiple times, and returns an object
+// (in JSON string format) containing the counters.
+// The 'end' invocation tears down the listeners, and should be invoked once
+// at the end to clean up. Returns true on success, false on failure.
+function chromeTouchEventCounter(operation) {
+ function chromeProcessCounter() {
+ addMessageListener('start', function() {
+ Components.utils.import('resource://gre/modules/Services.jsm');
+ var topWin = Services.wm.getMostRecentWindow('navigator:browser');
+ if (typeof topWin.eventCounts != 'undefined') {
+ dump('Found pre-existing eventCounts object on the top window!\n');
+ return false;
+ }
+ topWin.eventCounts = { 'touchstart': 0, 'touchmove': 0, 'touchend': 0 };
+ topWin.counter = function(e) {
+ topWin.eventCounts[e.type]++;
+ }
+
+ topWin.addEventListener('touchstart', topWin.counter, { passive: true });
+ topWin.addEventListener('touchmove', topWin.counter, { passive: true });
+ topWin.addEventListener('touchend', topWin.counter, { passive: true });
+
+ return true;
+ });
+
+ addMessageListener('report', function() {
+ Components.utils.import('resource://gre/modules/Services.jsm');
+ var topWin = Services.wm.getMostRecentWindow('navigator:browser');
+ return JSON.stringify(topWin.eventCounts);
+ });
+
+ addMessageListener('end', function() {
+ Components.utils.import('resource://gre/modules/Services.jsm');
+ var topWin = Services.wm.getMostRecentWindow('navigator:browser');
+ if (typeof topWin.eventCounts == 'undefined') {
+ dump('The eventCounts object was not found on the top window!\n');
+ return false;
+ }
+ topWin.removeEventListener('touchstart', topWin.counter);
+ topWin.removeEventListener('touchmove', topWin.counter);
+ topWin.removeEventListener('touchend', topWin.counter);
+ delete topWin.counter;
+ delete topWin.eventCounts;
+ return true;
+ });
+ }
+
+ if (typeof chromeTouchEventCounter.chromeHelper == 'undefined') {
+ // This is the first time getSnapshot is being called; do initialization
+ chromeTouchEventCounter.chromeHelper = SpecialPowers.loadChromeScript(chromeProcessCounter);
+ SimpleTest.registerCleanupFunction(function() { chromeTouchEventCounter.chromeHelper.destroy() });
+ }
+
+ return chromeTouchEventCounter.chromeHelper.sendSyncMessage(operation, "");
+}
+
+// Simple wrapper that waits until the chrome process has seen |count| instances
+// of the |eventType| event. Returns true on success, and false if 10 seconds
+// go by without the condition being satisfied.
+function waitFor(eventType, count) {
+ var start = Date.now();
+ while (JSON.parse(chromeTouchEventCounter('report'))[eventType] != count) {
+ if (Date.now() - start > 10000) {
+ // It's taking too long, let's abort
+ return false;
+ }
+ }
+ return true;
+}
+
+function* test(testDriver) {
+ // The main part of this test should run completely before the child process'
+ // main-thread deals with the touch event, so check to make sure that happens.
+ document.body.addEventListener('touchstart', failure, { passive: true });
+
+ // What we want here is to synthesize all of the touch events (from this code in
+ // the child process), and have the chrome process generate and process them,
+ // but not allow the events to be dispatched back into the child process until
+ // later. This allows us to ensure that the APZ in the chrome process is not
+ // waiting for the child process to send notifications upon processing the
+ // events. If it were doing so, the APZ would block and this test would fail.
+
+ // In order to actually implement this, we call the synthesize functions with
+ // a async callback in between. The synthesize functions just queue up a
+ // runnable on the child process main thread and return immediately, so with
+ // the async callbacks, the child process main thread queue looks like
+ // this after we're done setting it up:
+ // synthesizeTouchStart
+ // callback testDriver
+ // synthesizeTouchMove
+ // callback testDriver
+ // ...
+ // synthesizeTouchEnd
+ // callback testDriver
+ //
+ // If, after setting up this queue, we yield once, the first synthesization and
+ // callback will run - this will send a synthesization message to the chrome
+ // process, and return control back to us right away. When the chrome process
+ // processes with the synthesized event, it will dispatch the DOM touch event
+ // back to the child process over IPC, which will go into the end of the child
+ // process main thread queue, like so:
+ // synthesizeTouchStart (done)
+ // invoke testDriver (done)
+ // synthesizeTouchMove
+ // invoke testDriver
+ // ...
+ // synthesizeTouchEnd
+ // invoke testDriver
+ // handle DOM touchstart <-- touchstart goes at end of queue
+ //
+ // As we continue yielding one at a time, the synthesizations run, and the
+ // touch events get added to the end of the queue. As we yield, we take
+ // snapshots in the chrome process, to make sure that the APZ has started
+ // scrolling even though we know we haven't yet processed the DOM touch events
+ // in the child process yet.
+ //
+ // Note that the "async callback" we use here is SpecialPowers.executeSoon,
+ // because nothing else does exactly what we want:
+ // - setTimeout(..., 0) does not maintain ordering, because it respects the
+ // time delta provided (i.e. the callback can jump the queue to meet its
+ // deadline).
+ // - SpecialPowers.spinEventLoop and SpecialPowers.executeAfterFlushingMessageQueue
+ // are not e10s friendly, and can get arbitrarily delayed due to IPC
+ // round-trip time.
+ // - SimpleTest.executeSoon has a codepath that delegates to setTimeout, so
+ // is less reliable if it ever decides to switch to that codepath.
+
+ // The other problem we need to deal with is the asynchronicity in the chrome
+ // process. That is, we might request a snapshot before the chrome process has
+ // actually synthesized the event and processed it. To guard against this, we
+ // register a thing in the chrome process that counts the touch events that
+ // have been dispatched, and poll that thing synchronously in order to make
+ // sure we only snapshot after the event in question has been processed.
+ // That's what the chromeTouchEventCounter business is all about. The sync
+ // polling looks bad but in practice only ends up needing to poll once or
+ // twice before the condition is satisfied, and as an extra precaution we add
+ // a time guard so it fails after 10s of polling.
+
+ // So, here we go...
+
+ // Set up the chrome process touch listener
+ ok(chromeTouchEventCounter('start'), "Chrome touch counter registered");
+
+ // Set up the child process events and callbacks
+ var scroller = document.getElementById('scroller');
+ synthesizeNativeTouch(scroller, 10, 110, SpecialPowers.DOMWindowUtils.TOUCH_CONTACT, null, 0);
+ SpecialPowers.executeSoon(testDriver);
+ for (var i = 1; i < 10; i++) {
+ synthesizeNativeTouch(scroller, 10, 110 - (i * 10), SpecialPowers.DOMWindowUtils.TOUCH_CONTACT, null, 0);
+ SpecialPowers.executeSoon(testDriver);
+ }
+ synthesizeNativeTouch(scroller, 10, 10, SpecialPowers.DOMWindowUtils.TOUCH_REMOVE, null, 0);
+ SpecialPowers.executeSoon(testDriver);
+ ok(true, "Finished setting up event queue");
+
+ // Get our baseline snapshot
+ var rect = rectRelativeToScreen(scroller);
+ var lastSnapshot = getSnapshot(rect);
+ ok(true, "Got baseline snapshot");
+
+ yield; // this will tell the chrome process to synthesize the touchstart event
+ // and then we wait to make sure it got processed:
+ ok(waitFor('touchstart', 1), "Touchstart processed in chrome process");
+
+ // Loop through the touchmove events
+ for (var i = 1; i < 10; i++) {
+ yield;
+ ok(waitFor('touchmove', i), "Touchmove processed in chrome process");
+
+ var snapshot = getSnapshot(rect);
+ if (i == 1) {
+ // The first touchmove is consumed to get us into the panning state, so
+ // no actual panning occurs
+ ok(lastSnapshot == snapshot, "Snapshot 1 was the same as baseline");
+ } else {
+ ok(lastSnapshot != snapshot, "Snapshot " + i + " was different from the previous one");
+ }
+ lastSnapshot = snapshot;
+ }
+
+ // Wait for the touchend as well, just for good form
+ yield;
+ ok(waitFor('touchend', 1), "Touchend processed in chrome process");
+
+ // Clean up the chrome process hooks
+ chromeTouchEventCounter('end');
+
+ // Now we are going to release our grip on the child process main thread,
+ // so that all the DOM events that were queued up can be processed. We
+ // register a touchstart listener to make sure this happens.
+ document.body.removeEventListener('touchstart', failure);
+ document.body.addEventListener('touchstart', success, { passive: true });
+ yield flushApzRepaints(testDriver);
+ ok(success.triggered, "The touchstart event handler was triggered after snapshotting completed");
+ document.body.removeEventListener('touchstart', success);
+}
+
+if (SpecialPowers.isMainProcess()) {
+ // This is probably android, where everything is single-process. The
+ // test structure depends on e10s, so the test won't run properly on
+ // this platform. Skip it
+ ok(true, "Skipping test because it is designed to run from the content process");
+ subtestDone();
+} else {
+ waitUntilApzStable()
+ .then(runContinuation(test))
+ .then(subtestDone);
+}
+
+ </script>
+</head>
+<body>
+ <div id="scroller" style="width: 400px; height: 400px; overflow: scroll; touch-action: pan-y">
+ <div style="width: 200px; height: 200px; background-color: lightgreen;">
+ This is a colored div that will move on the screen as the scroller scrolls.
+ </div>
+ <div style="width: 1000px; height: 1000px; background-color: lightblue">
+ This is a large div to make the scroller scrollable.
+ </div>
+</body>
+</html>
diff --git a/gfx/layers/apz/test/mochitest/mochitest.ini b/gfx/layers/apz/test/mochitest/mochitest.ini
new file mode 100644
index 000000000..09e62428c
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/mochitest.ini
@@ -0,0 +1,67 @@
+[DEFAULT]
+ support-files =
+ apz_test_native_event_utils.js
+ apz_test_utils.js
+ helper_basic_pan.html
+ helper_bug982141.html
+ helper_bug1151663.html
+ helper_bug1162771.html
+ helper_bug1271432.html
+ helper_bug1280013.html
+ helper_bug1285070.html
+ helper_bug1299195.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_long_tap.html
+ helper_scroll_inactive_perspective.html
+ helper_scroll_inactive_zindex.html
+ helper_scroll_on_position_fixed.html
+ helper_scrollto_tap.html
+ helper_subframe_style.css
+ helper_tall.html
+ helper_tap.html
+ helper_tap_fullzoom.html
+ helper_tap_passive.html
+ helper_touch_action.html
+ helper_touch_action_regions.html
+ helper_touch_action_complex.html
+ tags = apz
+[test_bug982141.html]
+[test_bug1151663.html]
+[test_bug1151667.html]
+ skip-if = (os == 'android') # wheel events not supported on mobile
+[test_bug1253683.html]
+ skip-if = (os == 'android') # wheel events not supported on mobile
+[test_bug1277814.html]
+ skip-if = (os == 'android') # wheel events not supported on mobile
+[test_bug1304689.html]
+[test_bug1304689-2.html]
+[test_frame_reconstruction.html]
+[test_group_mouseevents.html]
+ 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_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
+[test_touch_listeners_impacting_wheel.html]
+ skip-if = (toolkit == 'android') || (toolkit == 'cocoa') # wheel events not supported on mobile, and synthesized wheel smooth-scrolling not supported on OS X
+[test_wheel_scroll.html]
+ skip-if = (os == 'android') # wheel events not supported on mobile
+[test_wheel_transactions.html]
+ skip-if = (os == 'android') # wheel events not supported on mobile
diff --git a/gfx/layers/apz/test/mochitest/test_bug1151663.html b/gfx/layers/apz/test/mochitest/test_bug1151663.html
new file mode 100644
index 000000000..10810c6ca
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_bug1151663.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1151663
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1151663</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 (isApzEnabled()) {
+ 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]]).then(function() {
+ w = window.open("helper_bug1151663.html", "_blank");
+ });
+ };
+ }
+
+ function finishTest() {
+ w.close();
+ SimpleTest.finish();
+ };
+
+ </script>
+</head>
+<body>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1151663">Mozilla Bug 1151663</a>
+</body>
+</html>
diff --git a/gfx/layers/apz/test/mochitest/test_bug1151667.html b/gfx/layers/apz/test/mochitest/test_bug1151667.html
new file mode 100644
index 000000000..88facf6e9
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_bug1151667.html
@@ -0,0 +1,65 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1151667
+-->
+<head>
+ <title>Test for Bug 1151667</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <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_native_event_utils.js"></script>
+ <script type="application/javascript" src="apz_test_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ #subframe {
+ margin-top: 100px;
+ height: 500px;
+ width: 500px;
+ overflow: scroll;
+ }
+ #subframe-content {
+ height: 1000px;
+ width: 500px;
+ /* the background is so that we can see it scroll*/
+ background: repeating-linear-gradient(#EEE, #EEE 100px, #DDD 100px, #DDD 200px);
+ }
+ #page-content {
+ height: 5000px;
+ width: 500px;
+ }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1151667">Mozilla Bug 1151667</a>
+<p id="display"></p>
+<div id="subframe">
+ <!-- This makes sure the subframe is scrollable -->
+ <div id="subframe-content"></div>
+</div>
+<!-- This makes sure the page is also scrollable, so it (rather than the subframe)
+ is considered the primary async-scrollable frame, and so the subframe isn't
+ layerized upon page load. -->
+<div id="page-content"></div>
+<pre id="test">
+<script type="application/javascript;version=1.7">
+
+function startTest() {
+ var subframe = document.getElementById('subframe');
+ synthesizeNativeWheelAndWaitForScrollEvent(subframe, 100, 150, 0, -10, continueTest);
+}
+
+function continueTest() {
+ var subframe = document.getElementById('subframe');
+ is(subframe.scrollTop > 0, true, "We should have scrolled the subframe down");
+ is(document.documentElement.scrollTop, 0, "We should not have scrolled the page");
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+waitUntilApzStable().then(startTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/gfx/layers/apz/test/mochitest/test_bug1253683.html b/gfx/layers/apz/test/mochitest/test_bug1253683.html
new file mode 100644
index 000000000..52c8e4a96
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_bug1253683.html
@@ -0,0 +1,59 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1253683
+-->
+<head>
+ <title>Test to ensure non-scrollable frames don't get layerized</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <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_native_event_utils.js"></script>
+ <script type="application/javascript" src="apz_test_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+ <p id="display"></p>
+ <div id="container" style="height: 500px; overflow:scroll">
+ <pre id="no_layer" style="background-color: #f5f5f5; margin: 15px; padding: 15px; margin-top: 100px; border: 1px solid #eee; overflow:scroll">sample code here</pre>
+ <div style="height: 5000px">spacer to make the 'container' div the root scrollable element</div>
+ </div>
+<pre id="test">
+<script type="application/javascript;version=1.7">
+
+function* test(testDriver) {
+ var container = document.getElementById('container');
+ var no_layer = document.getElementById('no_layer');
+
+ // Check initial state
+ is(container.scrollTop, 0, "Initial scrollY should be 0");
+ ok(!isLayerized('no_layer'), "initially 'no_layer' should not be layerized");
+
+ // Scrolling over outer1 should layerize outer1, but not inner1.
+ yield moveMouseAndScrollWheelOver(no_layer, 10, 10, testDriver, true);
+ yield waitForAllPaints(function() {
+ flushApzRepaints(testDriver);
+ });
+
+ ok(container.scrollTop > 0, "We should have scrolled the body");
+ ok(!isLayerized('no_layer'), "no_layer should still not be layerized");
+}
+
+if (isApzEnabled()) {
+ SimpleTest.waitForExplicitFinish();
+
+ // Turn off displayport expiry so that we don't miss failures where the
+ // displayport is set and expired before we check for layerization.
+ // Also enable APZ test logging, since we use that data to determine whether
+ // a scroll frame was layerized.
+ pushPrefs([["apz.displayport_expiry_ms", 0],
+ ["apz.test.logging_enabled", true]])
+ .then(waitUntilApzStable)
+ .then(runContinuation(test))
+ .then(SimpleTest.finish);
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/gfx/layers/apz/test/mochitest/test_bug1277814.html b/gfx/layers/apz/test/mochitest/test_bug1277814.html
new file mode 100644
index 000000000..877286468
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_bug1277814.html
@@ -0,0 +1,106 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1277814
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1277814</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.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" src="apz_test_native_event_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+ function* test(testDriver) {
+ // Trigger the buggy scenario
+ var subframe = document.getElementById('bug1277814-div');
+ subframe.classList.add('a');
+
+ // The transform change is animated, so let's step through 1s of animation
+ var utils = SpecialPowers.getDOMWindowUtils(window);
+ for (var i = 0; i < 60; i++) {
+ utils.advanceTimeAndRefresh(16);
+ }
+ utils.restoreNormalRefresh();
+
+ // Wait for the layer tree with any updated dispatch-to-content region to
+ // get pushed over to the APZ
+ yield waitForAllPaints(function() {
+ flushApzRepaints(testDriver);
+ });
+
+ // Trigger layerization of the subframe by scrolling the wheel over it
+ yield moveMouseAndScrollWheelOver(subframe, 10, 10, testDriver);
+
+ // Give APZ the chance to compute a displayport, and content
+ // to render based on it.
+ yield waitForApzFlushedRepaints(testDriver);
+
+ // Examine the content-side APZ test data
+ var contentTestData = utils.getContentAPZTestData();
+
+ // Test that the scroll frame for the div 'bug1277814-div' appears in
+ // the APZ test data. The bug this test is for causes the displayport
+ // calculation for this scroll frame to go wrong, causing it not to
+ // become layerized.
+ contentTestData = convertTestData(contentTestData);
+ var foundIt = false;
+ for (var seqNo in contentTestData.paints) {
+ var paint = contentTestData.paints[seqNo];
+ for (var scrollId in paint) {
+ var scrollFrame = paint[scrollId];
+ if ('contentDescription' in scrollFrame &&
+ scrollFrame['contentDescription'].includes('bug1277814-div')) {
+ foundIt = true;
+ }
+ }
+ }
+ SimpleTest.ok(foundIt, "expected to find APZ test data for bug1277814-div");
+ }
+
+ if (isApzEnabled()) {
+ SimpleTest.waitForExplicitFinish();
+
+ pushPrefs([["apz.test.logging_enabled", true]])
+ .then(waitUntilApzStable)
+ .then(runContinuation(test))
+ .then(SimpleTest.finish);
+ }
+ </script>
+ <style>
+ #bug1277814-div
+ {
+ position: absolute;
+ left: 0;
+ top: 0;
+ padding: .5em;
+ overflow: auto;
+ color: white;
+ background: green;
+ max-width: 30em;
+ max-height: 6em;
+ visibility: hidden;
+ transform: scaleY(0);
+ transition: transform .15s ease-out, visibility 0s ease .15s;
+ }
+ #bug1277814-div.a
+ {
+ visibility: visible;
+ transform: scaleY(1);
+ transition: transform .15s ease-out;
+ }
+ </style>
+</head>
+<body>
+ <!-- Use a unique id because we'll be checking for it in the content
+ description logged in the APZ test data -->
+ <div id="bug1277814-div">
+ CoolCmd<br>CoolCmd<br>CoolCmd<br>CoolCmd<br>
+ CoolCmd<br>CoolCmd<br>CoolCmd<br>CoolCmd<br>
+ CoolCmd<br>CoolCmd<br>CoolCmd<br>CoolCmd<br>
+ CoolCmd<br>CoolCmd<br>CoolCmd<br>CoolCmd<br>
+ CoolCmd<br>CoolCmd<br>CoolCmd<br>CoolCmd<br>
+ <button>click me</button>
+</body>
+</html>
diff --git a/gfx/layers/apz/test/mochitest/test_bug1304689-2.html b/gfx/layers/apz/test/mochitest/test_bug1304689-2.html
new file mode 100644
index 000000000..356d7bcb3
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_bug1304689-2.html
@@ -0,0 +1,131 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1304689
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1285070</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
+ <script type="application/javascript" src="apz_test_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style type="text/css">
+ #outer {
+ height: 400px;
+ width: 415px;
+ overflow: scroll;
+ position: relative;
+ scroll-behavior: smooth;
+ }
+ #outer.contentBefore::before {
+ top: 0;
+ content: '';
+ display: block;
+ height: 2px;
+ position: absolute;
+ width: 100%;
+ z-index: 99;
+ }
+ </style>
+ <script type="application/javascript">
+
+function* test(testDriver) {
+ var utils = SpecialPowers.DOMWindowUtils;
+ var elm = document.getElementById('outer');
+
+ // Set margins on the element, to ensure it is layerized
+ utils.setDisplayPortMarginsForElement(0, 0, 0, 0, elm, /*priority*/ 1);
+ yield waitForAllPaints(function() {
+ flushApzRepaints(testDriver);
+ });
+
+ // Take control of the refresh driver
+ utils.advanceTimeAndRefresh(0);
+
+ // Start a smooth-scroll animation in the compositor and let it go a few
+ // frames, so that there is some "user scrolling" going on (per the comment
+ // in AsyncPanZoomController::NotifyLayersUpdated)
+ elm.scrollTop = 10;
+ utils.advanceTimeAndRefresh(16);
+ utils.advanceTimeAndRefresh(16);
+ utils.advanceTimeAndRefresh(16);
+ utils.advanceTimeAndRefresh(16);
+
+ // Do another scroll update but also do a frame reconstruction within the same
+ // tick of the refresh driver.
+ elm.scrollTop = 100;
+ elm.classList.add('contentBefore');
+
+ // Now let everything settle and all the animations run out
+ for (var i = 0; i < 60; i++) {
+ utils.advanceTimeAndRefresh(16);
+ }
+ utils.restoreNormalRefresh();
+
+ yield flushApzRepaints(testDriver);
+ is(elm.scrollTop, 100, "The scrollTop now should be y=100");
+}
+
+if (isApzEnabled()) {
+ SimpleTest.waitForExplicitFinish();
+ pushPrefs([["apz.displayport_expiry_ms", 0]])
+ .then(waitUntilApzStable)
+ .then(runContinuation(test))
+ .then(SimpleTest.finish);
+}
+
+ </script>
+</head>
+<body>
+ <div id="outer">
+ <div id="inner">
+ this is some scrollable text.<br>
+ this is a second line to make the scrolling more obvious.<br>
+ and a third for good measure.<br>
+ this is some scrollable text.<br>
+ this is a second line to make the scrolling more obvious.<br>
+ and a third for good measure.<br>
+ this is some scrollable text.<br>
+ this is a second line to make the scrolling more obvious.<br>
+ and a third for good measure.<br>
+ this is some scrollable text.<br>
+ this is a second line to make the scrolling more obvious.<br>
+ and a third for good measure.<br>
+ this is some scrollable text.<br>
+ this is a second line to make the scrolling more obvious.<br>
+ and a third for good measure.<br>
+ this is some scrollable text.<br>
+ this is a second line to make the scrolling more obvious.<br>
+ and a third for good measure.<br>
+ this is some scrollable text.<br>
+ this is a second line to make the scrolling more obvious.<br>
+ and a third for good measure.<br>
+ this is some scrollable text.<br>
+ this is a second line to make the scrolling more obvious.<br>
+ and a third for good measure.<br>
+ this is some scrollable text.<br>
+ this is a second line to make the scrolling more obvious.<br>
+ and a third for good measure.<br>
+ this is some scrollable text.<br>
+ this is a second line to make the scrolling more obvious.<br>
+ and a third for good measure.<br>
+ this is some scrollable text.<br>
+ this is a second line to make the scrolling more obvious.<br>
+ and a third for good measure.<br>
+ this is some scrollable text.<br>
+ this is a second line to make the scrolling more obvious.<br>
+ and a third for good measure.<br>
+ this is some scrollable text.<br>
+ this is a second line to make the scrolling more obvious.<br>
+ and a third for good measure.<br>
+ this is some scrollable text.<br>
+ this is a second line to make the scrolling more obvious.<br>
+ and a third for good measure.<br>
+ this is some scrollable text.<br>
+ this is a second line to make the scrolling more obvious.<br>
+ and a third for good measure.<br>
+ </div>
+ </div>
+</body>
+</html>
diff --git a/gfx/layers/apz/test/mochitest/test_bug1304689.html b/gfx/layers/apz/test/mochitest/test_bug1304689.html
new file mode 100644
index 000000000..a64f8a34e
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_bug1304689.html
@@ -0,0 +1,135 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1304689
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1285070</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
+ <script type="application/javascript" src="apz_test_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style type="text/css">
+ #outer {
+ height: 400px;
+ width: 415px;
+ overflow: scroll;
+ position: relative;
+ scroll-behavior: smooth;
+ }
+ #outer.instant {
+ scroll-behavior: auto;
+ }
+ #outer.contentBefore::before {
+ top: 0;
+ content: '';
+ display: block;
+ height: 2px;
+ position: absolute;
+ width: 100%;
+ z-index: 99;
+ }
+ </style>
+ <script type="application/javascript">
+
+function* test(testDriver) {
+ var utils = SpecialPowers.DOMWindowUtils;
+ var elm = document.getElementById('outer');
+
+ // Set margins on the element, to ensure it is layerized
+ utils.setDisplayPortMarginsForElement(0, 0, 0, 0, elm, /*priority*/ 1);
+ yield waitForAllPaints(function() {
+ flushApzRepaints(testDriver);
+ });
+
+ // Take control of the refresh driver
+ utils.advanceTimeAndRefresh(0);
+
+ // Start a smooth-scroll animation in the compositor and let it go a few
+ // frames, so that there is some "user scrolling" going on (per the comment
+ // in AsyncPanZoomController::NotifyLayersUpdated)
+ elm.scrollTop = 10;
+ utils.advanceTimeAndRefresh(16);
+ utils.advanceTimeAndRefresh(16);
+ utils.advanceTimeAndRefresh(16);
+ utils.advanceTimeAndRefresh(16);
+
+ // Do another scroll update but also do a frame reconstruction within the same
+ // tick of the refresh driver.
+ elm.classList.add('instant');
+ elm.scrollTop = 100;
+ elm.classList.add('contentBefore');
+
+ // Now let everything settle and all the animations run out
+ for (var i = 0; i < 60; i++) {
+ utils.advanceTimeAndRefresh(16);
+ }
+ utils.restoreNormalRefresh();
+
+ yield flushApzRepaints(testDriver);
+ is(elm.scrollTop, 100, "The scrollTop now should be y=100");
+}
+
+if (isApzEnabled()) {
+ SimpleTest.waitForExplicitFinish();
+ pushPrefs([["apz.displayport_expiry_ms", 0]])
+ .then(waitUntilApzStable)
+ .then(runContinuation(test))
+ .then(SimpleTest.finish);
+}
+
+ </script>
+</head>
+<body>
+ <div id="outer">
+ <div id="inner">
+ this is some scrollable text.<br>
+ this is a second line to make the scrolling more obvious.<br>
+ and a third for good measure.<br>
+ this is some scrollable text.<br>
+ this is a second line to make the scrolling more obvious.<br>
+ and a third for good measure.<br>
+ this is some scrollable text.<br>
+ this is a second line to make the scrolling more obvious.<br>
+ and a third for good measure.<br>
+ this is some scrollable text.<br>
+ this is a second line to make the scrolling more obvious.<br>
+ and a third for good measure.<br>
+ this is some scrollable text.<br>
+ this is a second line to make the scrolling more obvious.<br>
+ and a third for good measure.<br>
+ this is some scrollable text.<br>
+ this is a second line to make the scrolling more obvious.<br>
+ and a third for good measure.<br>
+ this is some scrollable text.<br>
+ this is a second line to make the scrolling more obvious.<br>
+ and a third for good measure.<br>
+ this is some scrollable text.<br>
+ this is a second line to make the scrolling more obvious.<br>
+ and a third for good measure.<br>
+ this is some scrollable text.<br>
+ this is a second line to make the scrolling more obvious.<br>
+ and a third for good measure.<br>
+ this is some scrollable text.<br>
+ this is a second line to make the scrolling more obvious.<br>
+ and a third for good measure.<br>
+ this is some scrollable text.<br>
+ this is a second line to make the scrolling more obvious.<br>
+ and a third for good measure.<br>
+ this is some scrollable text.<br>
+ this is a second line to make the scrolling more obvious.<br>
+ and a third for good measure.<br>
+ this is some scrollable text.<br>
+ this is a second line to make the scrolling more obvious.<br>
+ and a third for good measure.<br>
+ this is some scrollable text.<br>
+ this is a second line to make the scrolling more obvious.<br>
+ and a third for good measure.<br>
+ this is some scrollable text.<br>
+ this is a second line to make the scrolling more obvious.<br>
+ and a third for good measure.<br>
+ </div>
+ </div>
+</body>
+</html>
diff --git a/gfx/layers/apz/test/mochitest/test_bug982141.html b/gfx/layers/apz/test/mochitest/test_bug982141.html
new file mode 100644
index 000000000..9984b79ff
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_bug982141.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=982141
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 982141</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 (isApzEnabled()) {
+ SimpleTest.waitForExplicitFinish();
+
+ // Run the actual test in its own window, because it requires that the
+ // root APZC not 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]]).then(function() {
+ w = window.open("helper_bug982141.html", "_blank");
+ });
+ };
+ }
+
+ function finishTest() {
+ w.close();
+ SimpleTest.finish();
+ };
+
+ </script>
+</head>
+<body>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=982141">Mozilla Bug 982141</a>
+</body>
+</html>
diff --git a/gfx/layers/apz/test/mochitest/test_frame_reconstruction.html b/gfx/layers/apz/test/mochitest/test_frame_reconstruction.html
new file mode 100644
index 000000000..589fb2843
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_frame_reconstruction.html
@@ -0,0 +1,218 @@
+<!DOCTYPE html>
+<html>
+ <!--
+ https://bugzilla.mozilla.org/show_bug.cgi?id=1235899
+ -->
+ <head>
+ <title>Test for bug 1235899</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <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" src="apz_test_native_event_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ .outer {
+ height: 400px;
+ width: 415px;
+ overflow: hidden;
+ position: relative;
+ }
+ .inner {
+ height: 100%;
+ outline: none;
+ overflow-x: hidden;
+ overflow-y: scroll;
+ position: relative;
+ scroll-behavior: smooth;
+ }
+ .outer.contentBefore::before {
+ top: 0;
+ content: '';
+ display: block;
+ height: 2px;
+ position: absolute;
+ width: 100%;
+ z-index: 99;
+ }
+ </style>
+ </head>
+ <body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1235899">Mozilla Bug 1235899</a>
+<p id="display"></p>
+<div id="content">
+ <p>You should be able to fling this list without it stopping abruptly</p>
+ <div class="outer">
+ <div class="inner">
+ <ol>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ <li>Some text</li>
+ </ol>
+ </div>
+ </div>
+</div>
+
+<pre id="test">
+<script type="application/javascript;version=1.7">
+function* test(testDriver) {
+ var elm = document.getElementsByClassName('inner')[0];
+ elm.scrollTop = 0;
+ yield flushApzRepaints(testDriver);
+
+ // Take over control of the refresh driver and compositor
+ var utils = SpecialPowers.DOMWindowUtils;
+ utils.advanceTimeAndRefresh(0);
+
+ // Kick off an APZ smooth-scroll to 0,200
+ elm.scrollTo(0, 200);
+ yield waitForAllPaints(function() { setTimeout(testDriver, 0); });
+
+ // Let's do a couple of frames of the animation, and make sure it's going
+ utils.advanceTimeAndRefresh(16);
+ utils.advanceTimeAndRefresh(16);
+ yield flushApzRepaints(testDriver);
+ ok(elm.scrollTop > 0, "APZ animation in progress", "scrollTop is now " + elm.scrollTop);
+ ok(elm.scrollTop < 200, "APZ animation not yet completed", "scrollTop is now " + elm.scrollTop);
+
+ var frameReconstructionTriggered = 0;
+ // Register the listener that triggers the frame reconstruction
+ elm.onscroll = function() {
+ // Do the reconstruction
+ elm.parentNode.classList.add('contentBefore');
+ frameReconstructionTriggered++;
+ // schedule a thing to undo the changes above
+ setTimeout(function() {
+ elm.parentNode.classList.remove('contentBefore');
+ }, 0);
+ }
+
+ // and do a few more frames of the animation, this should trigger the listener
+ // and the frame reconstruction
+ utils.advanceTimeAndRefresh(16);
+ utils.advanceTimeAndRefresh(16);
+ yield flushApzRepaints(testDriver);
+ ok(elm.scrollTop < 200, "APZ animation not yet completed", "scrollTop is now " + elm.scrollTop);
+ ok(frameReconstructionTriggered > 0, "Frame reconstruction triggered", "reconstruction triggered " + frameReconstructionTriggered + " times");
+
+ // and now run to completion
+ for (var i = 0; i < 100; i++) {
+ utils.advanceTimeAndRefresh(16);
+ }
+ utils.restoreNormalRefresh();
+ yield waitForAllPaints(function() { setTimeout(testDriver, 0); });
+ yield flushApzRepaints(testDriver);
+
+ is(elm.scrollTop, 200, "Element should have scrolled by 200px");
+}
+
+if (isApzEnabled()) {
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.expectAssertions(0, 1); // this test triggers an assertion, see bug 1247050
+ waitUntilApzStable()
+ .then(runContinuation(test))
+ .then(SimpleTest.finish);
+}
+
+</script>
+</body>
+</html>
diff --git a/gfx/layers/apz/test/mochitest/test_group_mouseevents.html b/gfx/layers/apz/test/mochitest/test_group_mouseevents.html
new file mode 100644
index 000000000..dcf71f0cc
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_group_mouseevents.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Various mouse tests that spawn in new windows</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">
+
+var subtests = [
+ // Sanity test to synthesize a mouse click
+ {'file': 'helper_click.html?dtc=false'},
+ // Same as above, but with a dispatch-to-content region that exercises the
+ // main-thread notification codepaths for mouse events
+ {'file': 'helper_click.html?dtc=true'},
+ // Sanity test for click but with some mouse movement between the down and up
+ {'file': 'helper_drag_click.html'},
+ // Test for dragging on a fake-scrollbar element that scrolls the page
+ {'file': 'helper_drag_scroll.html'}
+];
+
+if (isApzEnabled()) {
+ SimpleTest.waitForExplicitFinish();
+ window.onload = function() {
+ runSubtestsSeriallyInFreshWindows(subtests)
+ .then(SimpleTest.finish);
+ };
+}
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/gfx/layers/apz/test/mochitest/test_group_pointerevents.html b/gfx/layers/apz/test/mochitest/test_group_pointerevents.html
new file mode 100644
index 000000000..2e8d7c240
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_group_pointerevents.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1285070
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1285070</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">
+
+ var subtests = [
+ {'file': 'helper_bug1285070.html', 'prefs': [["dom.w3c_pointer_events.enabled", true]]},
+ {'file': 'helper_bug1299195.html', 'prefs': [["dom.w3c_pointer_events.enabled", true]]}
+ ];
+
+ if (isApzEnabled()) {
+ SimpleTest.waitForExplicitFinish();
+ window.onload = function() {
+ runSubtestsSeriallyInFreshWindows(subtests)
+ .then(SimpleTest.finish);
+ };
+ }
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/gfx/layers/apz/test/mochitest/test_group_touchevents.html b/gfx/layers/apz/test/mochitest/test_group_touchevents.html
new file mode 100644
index 000000000..bc0261d46
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_group_touchevents.html
@@ -0,0 +1,104 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Various touch tests that spawn in new windows</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="apz_test_native_event_utils.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">
+
+var basic_pan_prefs = [
+ // Dropping the touch slop to 0 makes the tests easier to write because
+ // we can just do a one-pixel drag to get over the pan threshold rather
+ // than having to hard-code some larger value.
+ ["apz.touch_start_tolerance", "0.0"],
+ // The touchstart from the drag can turn into a long-tap if the touch-move
+ // events get held up. Try to prevent that by making long-taps require
+ // a 10 second hold. Note that we also cannot enable chaos mode on this
+ // test for this reason, since chaos mode can cause the long-press timer
+ // to fire sooner than the pref dictates.
+ ["ui.click_hold_context_menus.delay", 10000],
+ // The subtests in this test do touch-drags to pan the page, but we don't
+ // want those pans to turn into fling animations, so we increase the
+ // fling min velocity requirement absurdly high.
+ ["apz.fling_min_velocity_threshold", "10000"],
+ // The helper_div_pan's div gets a displayport on scroll, but if the
+ // test takes too long the displayport can expire before the new scroll
+ // position is synced back to the main thread. So we disable displayport
+ // expiry for these tests.
+ ["apz.displayport_expiry_ms", 0],
+];
+
+var touch_action_prefs = basic_pan_prefs.slice(); // make a copy
+touch_action_prefs.push(["layout.css.touch_action.enabled", true]);
+
+var isWindows = (getPlatform() == "windows");
+
+var subtests = [
+ // Simple tests to exercise basic panning behaviour
+ {'file': 'helper_basic_pan.html', 'prefs': basic_pan_prefs},
+ {'file': 'helper_div_pan.html', 'prefs': basic_pan_prefs},
+ {'file': 'helper_iframe_pan.html', 'prefs': basic_pan_prefs},
+
+ // Simple test to exercise touch-tapping behaviour
+ {'file': 'helper_tap.html'},
+ // Tapping, but with a full-zoom applied
+ {'file': 'helper_tap_fullzoom.html'},
+
+ // For the following two tests, disable displayport suppression to make sure it
+ // doesn't interfere with the test by scheduling paints non-deterministically.
+ {'file': 'helper_scrollto_tap.html?true', 'prefs': [["apz.paint_skipping.enabled", true]], 'dp_suppression': false},
+ {'file': 'helper_scrollto_tap.html?false', 'prefs': [["apz.paint_skipping.enabled", false]], 'dp_suppression': false},
+
+ // Taps on media elements to make sure the touchend event is delivered
+ // properly. We increase the long-tap timeout to ensure it doesn't get trip
+ // during the tap.
+ // Also this test (on Windows) cannot satisfy the OS requirement of providing
+ // an injected touch event every 100ms, because it waits for a paint between
+ // the touchstart and the touchend, so we have to use the "fake injection"
+ // code instead.
+ {'file': 'helper_bug1162771.html', 'prefs': [["ui.click_hold_context_menus.delay", 10000],
+ ["apz.test.fails_with_native_injection", isWindows]]},
+
+ // As with the previous test, this test cannot inject touch events every 100ms
+ // because it waits for a long-tap, so we have to use the "fake injection" code
+ // instead.
+ {'file': 'helper_long_tap.html', 'prefs': [["apz.test.fails_with_native_injection", isWindows]]},
+
+ // For the following test, we want to make sure APZ doesn't wait for a content
+ // response that is never going to arrive. To detect this we set the content response
+ // timeout to a day, so that the entire test times out and fails if APZ does
+ // end up waiting.
+ {'file': 'helper_tap_passive.html', 'prefs': [["apz.content_response_timeout", 24 * 60 * 60 * 1000],
+ ["apz.test.fails_with_native_injection", isWindows]]},
+
+ // Simple test to exercise touch-action CSS property
+ {'file': 'helper_touch_action.html', 'prefs': touch_action_prefs},
+ // More complex touch-action tests, with overlapping regions and such
+ {'file': 'helper_touch_action_complex.html', 'prefs': touch_action_prefs},
+ // Tests that touch-action CSS properties are handled in APZ without waiting
+ // on the main-thread, when possible
+ {'file': 'helper_touch_action_regions.html', 'prefs': touch_action_prefs},
+];
+
+if (isApzEnabled()) {
+ ok(window.TouchEvent, "Check if TouchEvent is supported (it should be, the test harness forces it on everywhere)");
+ if (getPlatform() == "android") {
+ // This has a lot of subtests, and Android emulators are slow.
+ SimpleTest.requestLongerTimeout(2);
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ window.onload = function() {
+ runSubtestsSeriallyInFreshWindows(subtests)
+ .then(SimpleTest.finish);
+ };
+}
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/gfx/layers/apz/test/mochitest/test_group_wheelevents.html b/gfx/layers/apz/test/mochitest/test_group_wheelevents.html
new file mode 100644
index 000000000..98c36f320
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_group_wheelevents.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Various wheel-scrolling tests that spawn in new windows</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">
+
+var prefs = [
+ // turn off smooth scrolling so that we don't have to wait for
+ // APZ animations to finish before sampling the scroll offset
+ ['general.smoothScroll', false],
+ // ensure that any mouse movement will trigger a new wheel transaction,
+ // because in this test we move the mouse a bunch and want to recalculate
+ // the target APZC after each such movement.
+ ['mousewheel.transaction.ignoremovedelay', 0],
+ ['mousewheel.transaction.timeout', 0]
+]
+
+var subtests = [
+ {'file': 'helper_scroll_on_position_fixed.html', 'prefs': prefs},
+ {'file': 'helper_bug1271432.html', 'prefs': prefs},
+ {'file': 'helper_scroll_inactive_perspective.html', 'prefs': prefs},
+ {'file': 'helper_scroll_inactive_zindex.html', 'prefs': prefs}
+];
+
+if (isApzEnabled()) {
+ SimpleTest.waitForExplicitFinish();
+ window.onload = function() {
+ runSubtestsSeriallyInFreshWindows(subtests)
+ .then(SimpleTest.finish);
+ };
+}
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/gfx/layers/apz/test/mochitest/test_group_zoom.html b/gfx/layers/apz/test/mochitest/test_group_zoom.html
new file mode 100644
index 000000000..4bf9c0bed
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_group_zoom.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Various zoom-related tests that spawn in new windows</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">
+
+var prefs = [
+ // We need the APZ paint logging information
+ ["apz.test.logging_enabled", true],
+ // Dropping the touch slop to 0 makes the tests easier to write because
+ // we can just do a one-pixel drag to get over the pan threshold rather
+ // than having to hard-code some larger value.
+ ["apz.touch_start_tolerance", "0.0"],
+ // The subtests in this test do touch-drags to pan the page, but we don't
+ // want those pans to turn into fling animations, so we increase the
+ // fling-stop threshold velocity to absurdly high.
+ ["apz.fling_stopped_threshold", "10000"],
+ // The helper_bug1280013's div gets a displayport on scroll, but if the
+ // test takes too long the displayport can expire before we read the value
+ // out of the test. So we disable displayport expiry for these tests.
+ ["apz.displayport_expiry_ms", 0],
+];
+
+var subtests = [
+ {'file': 'helper_bug1280013.html', 'prefs': prefs},
+];
+
+if (isApzEnabled()) {
+ SimpleTest.waitForExplicitFinish();
+ window.onload = function() {
+ runSubtestsSeriallyInFreshWindows(subtests)
+ .then(SimpleTest.finish);
+ };
+}
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/gfx/layers/apz/test/mochitest/test_interrupted_reflow.html b/gfx/layers/apz/test/mochitest/test_interrupted_reflow.html
new file mode 100644
index 000000000..05c5e5478
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_interrupted_reflow.html
@@ -0,0 +1,719 @@
+<!DOCTYPE html>
+<html>
+ <!--
+ https://bugzilla.mozilla.org/show_bug.cgi?id=1292781
+ -->
+ <head>
+ <title>Test for bug 1292781</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <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" src="apz_test_native_event_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ .outer {
+ height: 400px;
+ width: 415px;
+ overflow: hidden;
+ position: relative;
+ }
+ .inner {
+ height: 100%;
+ outline: none;
+ overflow-x: hidden;
+ overflow-y: scroll;
+ position: relative;
+ }
+ .inner div:nth-child(even) {
+ background-color: lightblue;
+ }
+ .inner div:nth-child(odd) {
+ background-color: lightgreen;
+ }
+ .outer.contentBefore::before {
+ top: 0;
+ content: '';
+ display: block;
+ height: 2px;
+ position: absolute;
+ width: 100%;
+ z-index: 99;
+ }
+ </style>
+ </head>
+ <body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1292781">Mozilla Bug 1292781</a>
+<p id="display"></p>
+<div id="content">
+ <p>The frame reconstruction should not leave this scrollframe in a bad state</p>
+ <div class="outer">
+ <div class="inner">
+ this is the top of the scrollframe.
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ this is near the top of the scrollframe.
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ this is near the bottom of the scrollframe.
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ <div>this is a box</div>
+ this is the bottom of the scrollframe.
+ </div>
+ </div>
+</div>
+
+<pre id="test">
+<script type="text/javascript">
+
+// Returns a list of async scroll offsets that the |inner| element had, one for
+// each paint.
+function getAsyncScrollOffsets(aPaintsToIgnore) {
+ var offsets = [];
+ var compositorTestData = SpecialPowers.getDOMWindowUtils(window).getCompositorAPZTestData();
+ var buckets = compositorTestData.paints.slice(aPaintsToIgnore);
+ ok(buckets.length >= 3, "Expected at least three paints in the compositor test data");
+ var childIsLayerized = false;
+ for (var i = 0; i < buckets.length; ++i) {
+ var apzcTree = buildApzcTree(convertScrollFrameData(buckets[i].scrollFrames));
+ var rcd = findRcdNode(apzcTree);
+ if (rcd == null) {
+ continue;
+ }
+ if (rcd.children.length > 0) {
+ // The child may not be layerized in the first few paints, but once it is
+ // layerized, it should stay layerized.
+ childIsLayerized = true;
+ }
+ if (!childIsLayerized) {
+ continue;
+ }
+
+ ok(rcd.children.length == 1, "Root content APZC has exactly one child");
+ var scroll = rcd.children[0].asyncScrollOffset;
+ var pieces = scroll.replace(/[()\s]+/g, '').split(',');
+ is(pieces.length, 2, "expected string of form (x,y)");
+ offsets.push({ x: parseInt(pieces[0]),
+ y: parseInt(pieces[1]) });
+ }
+ return offsets;
+}
+
+function* test(testDriver) {
+ var utils = SpecialPowers.DOMWindowUtils;
+
+ // The APZ test data accumulates whenever a test turns it on. We just want
+ // the data for this test, so we check how many frames are already recorded
+ // and discard those later.
+ var framesToSkip = SpecialPowers.getDOMWindowUtils(window).getCompositorAPZTestData().paints.length;
+
+ var elm = document.getElementsByClassName('inner')[0];
+ // Set a zero-margin displayport to ensure that the element is async-scrollable
+ // otherwise on Fennec it is not
+ utils.setDisplayPortMarginsForElement(0, 0, 0, 0, elm, 0);
+
+ var maxScroll = elm.scrollTopMax;
+ elm.scrollTop = maxScroll;
+ yield waitForAllPaints(function() {
+ flushApzRepaints(testDriver);
+ });
+
+ // Take control of the refresh driver
+ utils.advanceTimeAndRefresh(0);
+
+ // Force the next reflow to get interrupted
+ utils.forceReflowInterrupt();
+
+ // Make a change that triggers frame reconstruction, and then tick the refresh
+ // driver so that layout processes the pending restyles and then runs an
+ // interruptible reflow. That reflow *will* be interrupted (because of the flag
+ // we set above), and we should end up with a transient 0,0 scroll offset
+ // being sent to the compositor.
+ elm.parentNode.classList.add('contentBefore');
+ utils.advanceTimeAndRefresh(0);
+ // On android, and maybe non-e10s platforms generally, we need to manually
+ // kick the paint to send the layer transaction to the compositor.
+ yield waitForAllPaints(function() { setTimeout(testDriver, 0) });
+
+ // Read the main-thread scroll offset; although this is temporarily 0,0 that
+ // temporary value is never exposed to content - instead reading this value
+ // will finish doing the interrupted reflow from above and then report the
+ // correct scroll offset.
+ is(elm.scrollTop, maxScroll, "Main-thread scroll position was restored");
+
+ // .. and now flush everything to make sure the state gets pushed over to the
+ // compositor and APZ as well.
+ utils.restoreNormalRefresh();
+ yield waitForApzFlushedRepaints(testDriver);
+
+ // Now we pull the compositor data and check it. What we expect to see is that
+ // the scroll position goes to maxScroll, then drops to 0 and then goes back
+ // to maxScroll. This test is specifically testing that last bit - that it
+ // properly gets restored from 0 to maxScroll.
+ // The one hitch is that on Android this page is loaded with some amount of
+ // zoom, and the async scroll is in ParentLayerPixel coordinates, so it will
+ // not match maxScroll exactly. Since we can't reliably compute what that
+ // ParentLayer scroll will be, we just make sure the async scroll is nonzero
+ // and use the first value we encounter to verify that it got restored properly.
+ // The other alternative is to spawn this test into a new window with 1.0 zoom
+ // but I'm tired of doing that for pretty much every test.
+ var state = 0;
+ var asyncScrollOffsets = getAsyncScrollOffsets(framesToSkip);
+ dump("Got scroll offsets: " + JSON.stringify(asyncScrollOffsets) + "\n");
+ var maxScrollParentLayerPixels = maxScroll;
+ while (asyncScrollOffsets.length > 0) {
+ let offset = asyncScrollOffsets.shift();
+ switch (state) {
+ // 0 is the initial state, the scroll offset might be zero but should
+ // become non-zero from when we set scrollTop to scrollTopMax
+ case 0:
+ if (offset.y == 0) {
+ break;
+ }
+ if (getPlatform() == "android") {
+ ok(offset.y > 0, "Async scroll y of scrollframe is " + offset.y);
+ maxScrollParentLayerPixels = offset.y;
+ } else {
+ is(offset.y, maxScrollParentLayerPixels, "Async scroll y of scrollframe is " + offset.y);
+ }
+ state = 1;
+ break;
+
+ // state 1 starts out at maxScrollParentLayerPixels, should drop to 0
+ // because of the interrupted reflow putting the scroll into a transient
+ // zero state
+ case 1:
+ if (offset.y == maxScrollParentLayerPixels) {
+ break;
+ }
+ is(offset.y, 0, "Async scroll position was temporarily 0");
+ state = 2;
+ break;
+
+ // state 2 starts out the transient 0 scroll offset, and we expect the
+ // scroll position to get restored back to maxScrollParentLayerPixels
+ case 2:
+ if (offset.y == 0) {
+ break;
+ }
+ is(offset.y, maxScrollParentLayerPixels, "Async scroll y of scrollframe restored to " + offset.y);
+ state = 3;
+ break;
+
+ // Terminal state. The scroll position should stay at maxScrollParentLayerPixels
+ case 3:
+ is(offset.y, maxScrollParentLayerPixels, "Scroll position maintained");
+ break;
+ }
+ }
+ is(state, 3, "The scroll position did drop to 0 and then get restored properly");
+}
+
+if (isApzEnabled()) {
+ SimpleTest.waitForExplicitFinish();
+
+ pushPrefs([["apz.test.logging_enabled", true],
+ ["apz.displayport_expiry_ms", 0]])
+ .then(waitUntilApzStable)
+ .then(runContinuation(test))
+ .then(SimpleTest.finish);
+}
+
+</script>
+</body>
+</html>
diff --git a/gfx/layers/apz/test/mochitest/test_layerization.html b/gfx/layers/apz/test/mochitest/test_layerization.html
new file mode 100644
index 000000000..c74b181bd
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_layerization.html
@@ -0,0 +1,214 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1173580
+-->
+<head>
+ <title>Test for layerization</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <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_native_event_utils.js"></script>
+ <script type="application/javascript" src="apz_test_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <link rel="stylesheet" type="text/css" href="helper_subframe_style.css"/>
+ <style>
+ #container {
+ display: flex;
+ overflow: scroll;
+ height: 500px;
+ }
+ .outer-frame {
+ height: 500px;
+ overflow: scroll;
+ flex-basis: 100%;
+ background: repeating-linear-gradient(#CCC, #CCC 100px, #BBB 100px, #BBB 200px);
+ }
+ #container-content {
+ height: 200%;
+ }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1173580">APZ layerization tests</a>
+<p id="display"></p>
+<div id="container">
+ <div id="outer1" class="outer-frame">
+ <div id="inner1" class="inner-frame">
+ <div class="inner-content"></div>
+ </div>
+ </div>
+ <div id="outer2" class="outer-frame">
+ <div id="inner2" class="inner-frame">
+ <div class="inner-content"></div>
+ </div>
+ </div>
+ <iframe id="outer3" class="outer-frame" src="helper_iframe1.html"></iframe>
+ <iframe id="outer4" class="outer-frame" src="helper_iframe2.html"></iframe>
+<!-- The container-content div ensures 'container' is scrollable, so the
+ optimization that layerizes the primary async-scrollable frame on page
+ load layerizes it rather than its child subframes. -->
+ <div id="container-content"></div>
+</div>
+<pre id="test">
+<script type="application/javascript;version=1.7">
+
+// Scroll the mouse wheel over |element|.
+function scrollWheelOver(element, waitForScroll, testDriver) {
+ moveMouseAndScrollWheelOver(element, 10, 10, testDriver, waitForScroll);
+}
+
+const DISPLAYPORT_EXPIRY = 100;
+
+// This helper function produces another helper function, which, when invoked,
+// invokes the provided testDriver argument in a setTimeout 0. This is really
+// just useful in cases when there are no paints pending, because then
+// waitForAllPaints will invoke its callback synchronously. If we did
+// waitForAllPaints(testDriver) that might cause reentrancy into the testDriver
+// which is bad. This function works around that.
+function asyncWrapper(testDriver) {
+ return function() {
+ setTimeout(testDriver, 0);
+ };
+}
+
+function* test(testDriver) {
+ // Initially, nothing should be layerized.
+ ok(!isLayerized('outer1'), "initially 'outer1' should not be layerized");
+ ok(!isLayerized('inner1'), "initially 'inner1' should not be layerized");
+ ok(!isLayerized('outer2'), "initially 'outer2' should not be layerized");
+ ok(!isLayerized('inner2'), "initially 'inner2' should not be layerized");
+ ok(!isLayerized('outer3'), "initially 'outer3' should not be layerized");
+ ok(!isLayerized('inner3'), "initially 'inner3' should not be layerized");
+ ok(!isLayerized('outer4'), "initially 'outer4' should not be layerized");
+ ok(!isLayerized('inner4'), "initially 'inner4' should not be layerized");
+
+ // Scrolling over outer1 should layerize outer1, but not inner1.
+ yield scrollWheelOver(document.getElementById('outer1'), true, testDriver);
+ ok(isLayerized('outer1'), "scrolling 'outer1' should cause it to be layerized");
+ ok(!isLayerized('inner1'), "scrolling 'outer1' should not cause 'inner1' to be layerized");
+
+ // Scrolling over inner2 should layerize both outer2 and inner2.
+ yield scrollWheelOver(document.getElementById('inner2'), true, testDriver);
+ ok(isLayerized('inner2'), "scrolling 'inner2' should cause it to be layerized");
+ ok(isLayerized('outer2'), "scrolling 'inner2' should also cause 'outer2' to be layerized");
+
+ // The second half of the test repeats the same checks as the first half,
+ // but with an iframe as the outer scrollable frame.
+
+ // Scrolling over outer3 should layerize outer3, but not inner3.
+ yield scrollWheelOver(document.getElementById('outer3').contentDocument.documentElement, true, testDriver);
+ ok(isLayerized('outer3'), "scrolling 'outer3' should cause it to be layerized");
+ ok(!isLayerized('inner3'), "scrolling 'outer3' should not cause 'inner3' to be layerized");
+
+ // Scrolling over outer4 should layerize both outer4 and inner4.
+ yield scrollWheelOver(document.getElementById('outer4').contentDocument.getElementById('inner4'), true, testDriver);
+ ok(isLayerized('inner4'), "scrolling 'inner4' should cause it to be layerized");
+ ok(isLayerized('outer4'), "scrolling 'inner4' should also cause 'outer4' to be layerized");
+
+ // Now we enable displayport expiry, and verify that things are still
+ // layerized as they were before.
+ yield SpecialPowers.pushPrefEnv({"set": [["apz.displayport_expiry_ms", DISPLAYPORT_EXPIRY]]}, testDriver);
+ ok(isLayerized('outer1'), "outer1 is still layerized after enabling expiry");
+ ok(!isLayerized('inner1'), "inner1 is still not layerized after enabling expiry");
+ ok(isLayerized('outer2'), "outer2 is still layerized after enabling expiry");
+ ok(isLayerized('inner2'), "inner2 is still layerized after enabling expiry");
+ ok(isLayerized('outer3'), "outer3 is still layerized after enabling expiry");
+ ok(!isLayerized('inner3'), "inner3 is still not layerized after enabling expiry");
+ ok(isLayerized('outer4'), "outer4 is still layerized after enabling expiry");
+ ok(isLayerized('inner4'), "inner4 is still layerized after enabling expiry");
+
+ // Now we trigger a scroll on some of the things still layerized, so that
+ // the displayport expiry gets triggered.
+
+ // Expire displayport with scrolling on outer1
+ yield scrollWheelOver(document.getElementById('outer1'), true, testDriver);
+ yield waitForAllPaints(function() {
+ flushApzRepaints(testDriver);
+ });
+ yield setTimeout(testDriver, DISPLAYPORT_EXPIRY);
+ yield waitForAllPaints(asyncWrapper(testDriver));
+ ok(!isLayerized('outer1'), "outer1 is no longer layerized after displayport expiry");
+ ok(!isLayerized('inner1'), "inner1 is still not layerized after displayport expiry");
+
+ // Expire displayport with scrolling on inner2
+ yield scrollWheelOver(document.getElementById('inner2'), true, testDriver);
+ yield waitForAllPaints(function() {
+ flushApzRepaints(testDriver);
+ });
+ // Once the expiry elapses, it will trigger expiry on outer2, so we check
+ // both, one at a time.
+ yield setTimeout(testDriver, DISPLAYPORT_EXPIRY);
+ yield waitForAllPaints(asyncWrapper(testDriver));
+ ok(!isLayerized('inner2'), "inner2 is no longer layerized after displayport expiry");
+ yield setTimeout(testDriver, DISPLAYPORT_EXPIRY);
+ yield waitForAllPaints(asyncWrapper(testDriver));
+ ok(!isLayerized('outer2'), "outer2 got de-layerized with inner2");
+
+ // Scroll on inner3. inner3 isn't layerized, and this will cause it to
+ // get layerized, but it will also trigger displayport expiration for inner3
+ // which will eventually trigger displayport expiration on inner3 and outer3.
+ // Note that the displayport expiration might actually happen before the wheel
+ // input is processed in the compositor (see bug 1246480 comment 3), and so
+ // we make sure not to wait for a scroll event here, since it may never fire.
+ // However, if we do get a scroll event while waiting for the expiry, we need
+ // to restart the expiry timer because the displayport expiry got reset. There's
+ // no good way that I can think of to deterministically avoid doing this.
+ let inner3 = document.getElementById('outer3').contentDocument.getElementById('inner3');
+ yield scrollWheelOver(inner3, false, testDriver);
+ yield waitForAllPaints(function() {
+ flushApzRepaints(testDriver);
+ });
+ var timerId = setTimeout(testDriver, DISPLAYPORT_EXPIRY);
+ var timeoutResetter = function() {
+ ok(true, "Got a scroll event; resetting timer...");
+ clearTimeout(timerId);
+ setTimeout(testDriver, DISPLAYPORT_EXPIRY);
+ // by not updating timerId we ensure that this listener resets the timeout
+ // at most once.
+ };
+ inner3.addEventListener('scroll', timeoutResetter, false);
+ yield; // wait for the setTimeout to elapse
+ inner3.removeEventListener('scroll', timeoutResetter, false);
+
+ yield waitForAllPaints(asyncWrapper(testDriver));
+ ok(!isLayerized('inner3'), "inner3 becomes unlayerized after expiry");
+ yield setTimeout(testDriver, DISPLAYPORT_EXPIRY);
+ yield waitForAllPaints(asyncWrapper(testDriver));
+ ok(!isLayerized('outer3'), "outer3 is no longer layerized after inner3 triggered expiry");
+
+ // Scroll outer4 and wait for the expiry. It should NOT get expired because
+ // inner4 is still layerized
+ yield scrollWheelOver(document.getElementById('outer4').contentDocument.documentElement, true, testDriver);
+ yield waitForAllPaints(function() {
+ flushApzRepaints(testDriver);
+ });
+ // Wait for the expiry to elapse
+ yield setTimeout(testDriver, DISPLAYPORT_EXPIRY);
+ yield waitForAllPaints(asyncWrapper(testDriver));
+ ok(isLayerized('inner4'), "inner4 is still layerized because it never expired");
+ ok(isLayerized('outer4'), "outer4 is still layerized because inner4 is still layerized");
+}
+
+if (isApzEnabled()) {
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.requestFlakyTimeout("we are testing code that measures an actual timeout");
+ SimpleTest.expectAssertions(0, 8); // we get a bunch of "ASSERTION: Bounds computation mismatch" sometimes (bug 1232856)
+
+ // Disable smooth scrolling, because it results in long-running scroll
+ // animations that can result in a 'scroll' event triggered by an earlier
+ // wheel event as corresponding to a later wheel event.
+ // Also enable APZ test logging, since we use that data to determine whether
+ // a scroll frame was layerized.
+ pushPrefs([["general.smoothScroll", false],
+ ["apz.displayport_expiry_ms", 0],
+ ["apz.test.logging_enabled", true]])
+ .then(waitUntilApzStable)
+ .then(runContinuation(test))
+ .then(SimpleTest.finish);
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/gfx/layers/apz/test/mochitest/test_scroll_inactive_bug1190112.html b/gfx/layers/apz/test/mochitest/test_scroll_inactive_bug1190112.html
new file mode 100644
index 000000000..3349ef1ab
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_scroll_inactive_bug1190112.html
@@ -0,0 +1,541 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test scrolling flattened inactive frames</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <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>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+<style>
+p {
+ width:200px;
+ height:200px;
+ border:solid 1px black;
+ overflow:auto;
+}
+</style>
+</head>
+<body>
+<div id="iframe-body" style="overflow: auto; height: 1000px">
+<hr>
+<hr>
+<hr>
+<p>
+1 <br>
+2 <br>
+3 <br>
+4 <br>
+5 <br>
+6 <br>
+7 <br>
+8 <br>
+9 <br>
+10 <br>
+11 <br>
+12 <br>
+13 <br>
+14 <br>
+15 <br>
+16 <br>
+17 <br>
+18 <br>
+19 <br>
+20 <br>
+21 <br>
+22 <br>
+23 <br>
+24 <br>
+25 <br>
+26 <br>
+27 <br>
+28 <br>
+29 <br>
+30 <br>
+31 <br>
+32 <br>
+33 <br>
+34 <br>
+35 <br>
+36 <br>
+37 <br>
+38 <br>
+39 <br>
+40 <br>
+
+</p><p id="subframe">
+1 <br>
+2 <br>
+3 <br>
+4 <br>
+5 <br>
+6 <br>
+7 <br>
+8 <br>
+9 <br>
+10 <br>
+11 <br>
+12 <br>
+13 <br>
+14 <br>
+15 <br>
+16 <br>
+17 <br>
+18 <br>
+19 <br>
+20 <br>
+21 <br>
+22 <br>
+23 <br>
+24 <br>
+25 <br>
+26 <br>
+27 <br>
+28 <br>
+29 <br>
+30 <br>
+31 <br>
+32 <br>
+33 <br>
+34 <br>
+35 <br>
+36 <br>
+37 <br>
+38 <br>
+39 <br>
+40 <br>
+
+</p><p>
+1 <br>
+2 <br>
+3 <br>
+4 <br>
+5 <br>
+6 <br>
+7 <br>
+8 <br>
+9 <br>
+10 <br>
+11 <br>
+12 <br>
+13 <br>
+14 <br>
+15 <br>
+16 <br>
+17 <br>
+18 <br>
+19 <br>
+20 <br>
+21 <br>
+22 <br>
+23 <br>
+24 <br>
+25 <br>
+26 <br>
+27 <br>
+28 <br>
+29 <br>
+30 <br>
+31 <br>
+32 <br>
+33 <br>
+34 <br>
+35 <br>
+36 <br>
+37 <br>
+38 <br>
+39 <br>
+40 <br>
+
+</p><p>
+1 <br>
+2 <br>
+3 <br>
+4 <br>
+5 <br>
+6 <br>
+7 <br>
+8 <br>
+9 <br>
+10 <br>
+11 <br>
+12 <br>
+13 <br>
+14 <br>
+15 <br>
+16 <br>
+17 <br>
+18 <br>
+19 <br>
+20 <br>
+21 <br>
+22 <br>
+23 <br>
+24 <br>
+25 <br>
+26 <br>
+27 <br>
+28 <br>
+29 <br>
+30 <br>
+31 <br>
+32 <br>
+33 <br>
+34 <br>
+35 <br>
+36 <br>
+37 <br>
+38 <br>
+39 <br>
+40 <br>
+
+</p><p>
+1 <br>
+2 <br>
+3 <br>
+4 <br>
+5 <br>
+6 <br>
+7 <br>
+8 <br>
+9 <br>
+10 <br>
+11 <br>
+12 <br>
+13 <br>
+14 <br>
+15 <br>
+16 <br>
+17 <br>
+18 <br>
+19 <br>
+20 <br>
+21 <br>
+22 <br>
+23 <br>
+24 <br>
+25 <br>
+26 <br>
+27 <br>
+28 <br>
+29 <br>
+30 <br>
+31 <br>
+32 <br>
+33 <br>
+34 <br>
+35 <br>
+36 <br>
+37 <br>
+38 <br>
+39 <br>
+40 <br>
+
+</p><p>
+1 <br>
+2 <br>
+3 <br>
+4 <br>
+5 <br>
+6 <br>
+7 <br>
+8 <br>
+9 <br>
+10 <br>
+11 <br>
+12 <br>
+13 <br>
+14 <br>
+15 <br>
+16 <br>
+17 <br>
+18 <br>
+19 <br>
+20 <br>
+21 <br>
+22 <br>
+23 <br>
+24 <br>
+25 <br>
+26 <br>
+27 <br>
+28 <br>
+29 <br>
+30 <br>
+31 <br>
+32 <br>
+33 <br>
+34 <br>
+35 <br>
+36 <br>
+37 <br>
+38 <br>
+39 <br>
+40 <br>
+
+</p><p>
+1 <br>
+2 <br>
+3 <br>
+4 <br>
+5 <br>
+6 <br>
+7 <br>
+8 <br>
+9 <br>
+10 <br>
+11 <br>
+12 <br>
+13 <br>
+14 <br>
+15 <br>
+16 <br>
+17 <br>
+18 <br>
+19 <br>
+20 <br>
+21 <br>
+22 <br>
+23 <br>
+24 <br>
+25 <br>
+26 <br>
+27 <br>
+28 <br>
+29 <br>
+30 <br>
+31 <br>
+32 <br>
+33 <br>
+34 <br>
+35 <br>
+36 <br>
+37 <br>
+38 <br>
+39 <br>
+40 <br>
+
+</p><p>
+1 <br>
+2 <br>
+3 <br>
+4 <br>
+5 <br>
+6 <br>
+7 <br>
+8 <br>
+9 <br>
+10 <br>
+11 <br>
+12 <br>
+13 <br>
+14 <br>
+15 <br>
+16 <br>
+17 <br>
+18 <br>
+19 <br>
+20 <br>
+21 <br>
+22 <br>
+23 <br>
+24 <br>
+25 <br>
+26 <br>
+27 <br>
+28 <br>
+29 <br>
+30 <br>
+31 <br>
+32 <br>
+33 <br>
+34 <br>
+35 <br>
+36 <br>
+37 <br>
+38 <br>
+39 <br>
+40 <br>
+
+</p><p>
+1 <br>
+2 <br>
+3 <br>
+4 <br>
+5 <br>
+6 <br>
+7 <br>
+8 <br>
+9 <br>
+10 <br>
+11 <br>
+12 <br>
+13 <br>
+14 <br>
+15 <br>
+16 <br>
+17 <br>
+18 <br>
+19 <br>
+20 <br>
+21 <br>
+22 <br>
+23 <br>
+24 <br>
+25 <br>
+26 <br>
+27 <br>
+28 <br>
+29 <br>
+30 <br>
+31 <br>
+32 <br>
+33 <br>
+34 <br>
+35 <br>
+36 <br>
+37 <br>
+38 <br>
+39 <br>
+40 <br>
+
+</p><p>
+1 <br>
+2 <br>
+3 <br>
+4 <br>
+5 <br>
+6 <br>
+7 <br>
+8 <br>
+9 <br>
+10 <br>
+11 <br>
+12 <br>
+13 <br>
+14 <br>
+15 <br>
+16 <br>
+17 <br>
+18 <br>
+19 <br>
+20 <br>
+21 <br>
+22 <br>
+23 <br>
+24 <br>
+25 <br>
+26 <br>
+27 <br>
+28 <br>
+29 <br>
+30 <br>
+31 <br>
+32 <br>
+33 <br>
+34 <br>
+35 <br>
+36 <br>
+37 <br>
+38 <br>
+39 <br>
+40 <br>
+
+</p><p>
+1 <br>
+2 <br>
+3 <br>
+4 <br>
+5 <br>
+6 <br>
+7 <br>
+8 <br>
+9 <br>
+10 <br>
+11 <br>
+12 <br>
+13 <br>
+14 <br>
+15 <br>
+16 <br>
+17 <br>
+18 <br>
+19 <br>
+20 <br>
+21 <br>
+22 <br>
+23 <br>
+24 <br>
+25 <br>
+26 <br>
+27 <br>
+28 <br>
+29 <br>
+30 <br>
+31 <br>
+32 <br>
+33 <br>
+34 <br>
+35 <br>
+36 <br>
+37 <br>
+38 <br>
+39 <br>
+40 <br>
+
+</p>
+</div>
+<script clss="testbody" type="text/javascript;version=1.7">
+function ScrollTops() {
+ this.outerScrollTop = document.getElementById('iframe-body').scrollTop;
+ this.innerScrollTop = document.getElementById('subframe').scrollTop;
+}
+
+var DefaultEvent = {
+ deltaMode: WheelEvent.DOM_DELTA_LINE,
+ deltaX: 0, deltaY: 1,
+ lineOrPageDeltaX: 0, lineOrPageDeltaY: 1,
+};
+
+function test() {
+ var subframe = document.getElementById('subframe');
+ var oldpos = new ScrollTops();
+ sendWheelAndPaint(subframe, 10, 10, DefaultEvent, function () {
+ var newpos = new ScrollTops();
+ ok(oldpos.outerScrollTop == newpos.outerScrollTop, "viewport should not have scrolled");
+ ok(oldpos.innerScrollTop != newpos.innerScrollTop, "subframe should have scrolled");
+ doOuterScroll(subframe, newpos);
+ });
+}
+
+function doOuterScroll(subframe, oldpos) {
+ var outer = document.getElementById('iframe-body');
+ sendWheelAndPaint(outer, 20, 5, DefaultEvent, function () {
+ var newpos = new ScrollTops();
+ ok(oldpos.outerScrollTop != newpos.outerScrollTop, "viewport should have scrolled");
+ ok(oldpos.innerScrollTop == newpos.innerScrollTop, "subframe should not have scrolled");
+ doInnerScrollAgain(subframe, newpos);
+ });
+}
+
+function doInnerScrollAgain(subframe, oldpos) {
+ sendWheelAndPaint(subframe, 10, 10, DefaultEvent, function () {
+ var newpos = new ScrollTops();
+ ok(oldpos.outerScrollTop == newpos.outerScrollTop, "viewport should not have scrolled");
+ ok(oldpos.innerScrollTop != newpos.innerScrollTop, "subframe should have scrolled");
+ SimpleTest.finish();
+ });
+}
+
+SimpleTest.testInChaosMode();
+SimpleTest.waitForExplicitFinish();
+
+pushPrefs([['general.smoothScroll', false],
+ ['mousewheel.transaction.timeout', 0],
+ ['mousewheel.transaction.ignoremovedelay', 0]])
+.then(waitUntilApzStable)
+.then(test);
+
+</script>
+</body>
+</html>
diff --git a/gfx/layers/apz/test/mochitest/test_scroll_inactive_flattened_frame.html b/gfx/layers/apz/test/mochitest/test_scroll_inactive_flattened_frame.html
new file mode 100644
index 000000000..51e16aab9
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_scroll_inactive_flattened_frame.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test scrolling flattened inactive frames</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <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>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<div id="container" style="height: 300px; width: 600px; overflow: auto; background: yellow">
+ <div id="outer" style="height: 400px; width: 500px; overflow: auto; background: black">
+ <div id="inner" style="mix-blend-mode: screen; height: 800px; overflow: auto; background: purple">
+ </div>
+ </div>
+</div>
+<script class="testbody" type="text/javascript;version=1.7">
+function test() {
+ var container = document.getElementById('container');
+ var outer = document.getElementById('outer');
+ var inner = document.getElementById('inner');
+ var outerScrollTop = outer.scrollTop;
+ var containerScrollTop = container.scrollTop;
+ var event = {
+ deltaMode: WheelEvent.DOM_DELTA_LINE,
+ deltaX: 0,
+ deltaY: 10,
+ lineOrPageDeltaX: 0,
+ lineOrPageDeltaY: 10,
+ };
+ sendWheelAndPaint(inner, 20, 30, event, function () {
+ ok(container.scrollTop == containerScrollTop, "container scrollframe should not have scrolled");
+ ok(outer.scrollTop > outerScrollTop, "nested scrollframe should have scrolled");
+ SimpleTest.finish();
+ });
+}
+
+SimpleTest.testInChaosMode();
+SimpleTest.waitForExplicitFinish();
+
+pushPrefs([['general.smoothScroll', false],
+ ['mousewheel.transaction.timeout', 1000000]])
+.then(waitUntilApzStable)
+.then(test);
+
+</script>
+</body>
+</html>
diff --git a/gfx/layers/apz/test/mochitest/test_scroll_subframe_scrollbar.html b/gfx/layers/apz/test/mochitest/test_scroll_subframe_scrollbar.html
new file mode 100644
index 000000000..4d9da8c2c
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_scroll_subframe_scrollbar.html
@@ -0,0 +1,117 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test scrolling subframe scrollbars</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <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>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+<style>
+p {
+ width:200px;
+ height:200px;
+ border:solid 1px black;
+}
+</style>
+</head>
+<body>
+<p id="subframe">
+1 <br>
+2 <br>
+3 <br>
+4 <br>
+5 <br>
+6 <br>
+7 <br>
+8 <br>
+9 <br>
+10 <br>
+11 <br>
+12 <br>
+13 <br>
+14 <br>
+15 <br>
+16 <br>
+17 <br>
+18 <br>
+19 <br>
+20 <br>
+21 <br>
+22 <br>
+23 <br>
+24 <br>
+25 <br>
+26 <br>
+27 <br>
+28 <br>
+29 <br>
+30 <br>
+31 <br>
+32 <br>
+33 <br>
+34 <br>
+35 <br>
+36 <br>
+37 <br>
+38 <br>
+39 <br>
+40 <br>
+</p>
+<script clss="testbody" type="text/javascript;version=1.7">
+
+var DefaultEvent = {
+ deltaMode: WheelEvent.DOM_DELTA_LINE,
+ deltaX: 0, deltaY: 1,
+ lineOrPageDeltaX: 0, lineOrPageDeltaY: 1,
+};
+
+var ScrollbarWidth = 0;
+
+function test() {
+ var subframe = document.getElementById('subframe');
+ var oldClientWidth = subframe.clientWidth;
+
+ subframe.style.overflow = 'auto';
+ subframe.getBoundingClientRect();
+
+ waitForAllPaintsFlushed(function () {
+ ScrollbarWidth = oldClientWidth - subframe.clientWidth;
+ if (!ScrollbarWidth) {
+ // Probably we have overlay scrollbars - abort the test.
+ ok(true, "overlay scrollbars - skipping test");
+ SimpleTest.finish();
+ return;
+ }
+
+ ok(subframe.scrollHeight > subframe.clientHeight, "subframe should have scrollable content");
+ testScrolling(subframe);
+ });
+}
+
+function testScrolling(subframe) {
+ // Send a wheel event roughly to where we think the trackbar is. We pick a
+ // point at the bottom, in the middle of the trackbar, where the slider is
+ // unlikely to be (since it starts at the top).
+ var posX = subframe.clientWidth + (ScrollbarWidth / 2);
+ var posY = subframe.clientHeight - 20;
+
+ var oldScrollTop = subframe.scrollTop;
+
+ sendWheelAndPaint(subframe, posX, posY, DefaultEvent, function () {
+ ok(subframe.scrollTop > oldScrollTop, "subframe should have scrolled");
+ SimpleTest.finish();
+ });
+}
+
+SimpleTest.waitForExplicitFinish();
+
+pushPrefs([['general.smoothScroll', false],
+ ['mousewheel.transaction.timeout', 0],
+ ['mousewheel.transaction.ignoremovedelay', 0]])
+.then(waitUntilApzStable)
+.then(test);
+
+</script>
+</body>
+</html>
diff --git a/gfx/layers/apz/test/mochitest/test_smoothness.html b/gfx/layers/apz/test/mochitest/test_smoothness.html
new file mode 100644
index 000000000..88373957a
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_smoothness.html
@@ -0,0 +1,77 @@
+<html>
+<head>
+ <title>Test Frame Uniformity While Scrolling</title>
+ <script type="text/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="apz_test_native_event_utils.js"></script>
+
+ <style>
+ #content {
+ height: 5000px;
+ background: repeating-linear-gradient(#EEE, #EEE 100px, #DDD 100px, #DDD 200px);
+ }
+ </style>
+ <script type="text/javascript">
+ var scrollEvents = 100;
+ var i = 0;
+ var testPref = "gfx.vsync.collect-scroll-transforms";
+ // Scroll points
+ var x = 100;
+ var y = 150;
+
+ SimpleTest.waitForExplicitFinish();
+ var utils = _getDOMWindowUtils(window);
+
+ function sendScrollEvent(aRafTimestamp) {
+ var scrollDiv = document.getElementById("content");
+
+ if (i < scrollEvents) {
+ i++;
+ // Scroll diff
+ var dx = 0;
+ var dy = -10; // Negative to scroll down
+ synthesizeNativeWheelAndWaitForWheelEvent(scrollDiv, x, y, dx, dy);
+ window.requestAnimationFrame(sendScrollEvent);
+ } else {
+ // Locally, with silk and apz + e10s, retina 15" mbp usually get ~1.0 - 1.5
+ // w/o silk + e10s + apz, I get up to 7. Lower is better.
+ // Windows, I get ~3. Values are not valid w/o hardware vsync
+ var uniformities = _getDOMWindowUtils().getFrameUniformityTestData();
+ for (var j = 0; j < uniformities.layerUniformities.length; j++) {
+ var layerResult = uniformities.layerUniformities[j];
+ var layerAddr = layerResult.layerAddress;
+ var uniformity = layerResult.frameUniformity;
+ var msg = "Layer: " + layerAddr.toString(16) + " Uniformity: " + uniformity;
+ SimpleTest.ok((uniformity >= 0) && (uniformity < 4.0), msg);
+ }
+ SimpleTest.finish();
+ }
+ }
+
+ function startTest() {
+ window.requestAnimationFrame(sendScrollEvent);
+ }
+
+ window.onload = function() {
+ var apzEnabled = SpecialPowers.getBoolPref("layers.async-pan-zoom.enabled");
+ if (!apzEnabled) {
+ SimpleTest.ok(true, "APZ not enabled, skipping test");
+ SimpleTest.finish();
+ }
+
+ SpecialPowers.pushPrefEnv({
+ "set" : [
+ [testPref, true]
+ ]
+ }, startTest);
+ }
+ </script>
+</head>
+
+<body>
+ <div id="content">
+ </div>
+</body>
+</html>
diff --git a/gfx/layers/apz/test/mochitest/test_touch_listeners_impacting_wheel.html b/gfx/layers/apz/test/mochitest/test_touch_listeners_impacting_wheel.html
new file mode 100644
index 000000000..913269a67
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_touch_listeners_impacting_wheel.html
@@ -0,0 +1,114 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1203140
+-->
+<head>
+ <title>Test for Bug 1203140</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <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" src="apz_test_native_event_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1203140">Mozilla Bug 1203140</a>
+<p id="display"></p>
+<div id="content" style="overflow-y:scroll; height: 400px">
+ <p>The box below has a touch listener and a passive wheel listener. With touch events disabled, APZ shouldn't wait for any listeners.</p>
+ <div id="box" style="width: 200px; height: 200px; background-color: blue"></div>
+ <div style="height: 1000px; width: 10px">Div to make 'content' scrollable</div>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+const kResponseTimeoutMs = 2 * 60 * 1000; // 2 minutes
+
+function takeSnapshots(e) {
+ // Grab some snapshots, and make sure some of them are different (i.e. check
+ // the page is scrolling in the compositor, concurrently with this wheel
+ // listener running).
+ // Note that we want this function to take less time than the content response
+ // timeout, otherwise the scrolling will start even if we haven't returned,
+ // and that would invalidate purpose of the test.
+ var start = Date.now();
+ var lastSnapshot = null;
+ var success = false;
+
+ // Get the position of the 'content' div relative to the screen
+ var rect = rectRelativeToScreen(document.getElementById('content'));
+
+ for (var i = 0; i < 10; i++) {
+ SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(16);
+ var snapshot = getSnapshot(rect);
+ //dump("Took snapshot " + snapshot + "\n"); // this might help with debugging
+
+ if (lastSnapshot && lastSnapshot != snapshot) {
+ ok(true, "Found some different pixels in snapshot " + i + " compared to previous");
+ success = true;
+ }
+ lastSnapshot = snapshot;
+ }
+ ok(success, "Found some snapshots that were different");
+ ok((Date.now() - start) < kResponseTimeoutMs, "Snapshotting ran quickly enough");
+
+ // Until now, no scroll events will have been dispatched to content. That's
+ // because scroll events are dispatched on the main thread, which we've been
+ // hogging with the code above. At this point we restore the normal refresh
+ // behaviour and let the main thread go back to C++ code, so the scroll events
+ // fire and we unwind from the main test continuation.
+ SpecialPowers.DOMWindowUtils.restoreNormalRefresh();
+}
+
+function* test(testDriver) {
+ var box = document.getElementById('box');
+
+ // Ensure the div is layerized by scrolling it
+ yield moveMouseAndScrollWheelOver(box, 10, 10, testDriver);
+
+ box.addEventListener('touchstart', function(e) {
+ ok(false, "This should never be run");
+ }, false);
+ box.addEventListener('wheel', takeSnapshots, { capture: false, passive: true });
+
+ // Let the event regions and layerization propagate to the APZ
+ yield waitForAllPaints(function() {
+ flushApzRepaints(testDriver);
+ });
+
+ // Take over control of the refresh driver and compositor
+ var utils = SpecialPowers.DOMWindowUtils;
+ utils.advanceTimeAndRefresh(0);
+
+ // Trigger an APZ scroll using a wheel event. If APZ is waiting for a
+ // content response, it will wait for takeSnapshots to finish running before
+ // it starts scrolling, which will cause the checks in takeSnapshots to fail.
+ yield synthesizeNativeMouseMoveAndWaitForMoveEvent(box, 10, 10, testDriver);
+ yield synthesizeNativeWheelAndWaitForScrollEvent(box, 10, 10, 0, -50, testDriver);
+}
+
+if (isApzEnabled()) {
+ SimpleTest.waitForExplicitFinish();
+ // Disable touch events, so that APZ knows not to wait for touch listeners.
+ // Also explicitly set the content response timeout, so we know how long it
+ // is (see comment in takeSnapshots).
+ // Finally, enable smooth scrolling, so that the wheel-scroll we do as part
+ // of the test triggers an APZ animation rather than doing an instant scroll.
+ // Note that this pref doesn't work for the synthesized wheel events on OS X,
+ // those are hard-coded to be instant scrolls.
+ pushPrefs([["dom.w3c_touch_events.enabled", 0],
+ ["apz.content_response_timeout", kResponseTimeoutMs],
+ ["general.smoothscroll", true]])
+ .then(waitUntilApzStable)
+ .then(runContinuation(test))
+ .then(SimpleTest.finish);
+}
+
+</script>
+</pre>
+
+</body>
+</html>
diff --git a/gfx/layers/apz/test/mochitest/test_wheel_scroll.html b/gfx/layers/apz/test/mochitest/test_wheel_scroll.html
new file mode 100644
index 000000000..479478d42
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_wheel_scroll.html
@@ -0,0 +1,106 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1013412
+-->
+<head>
+ <title>Test for Bug 1013412</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <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" src="apz_test_native_event_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ #content {
+ height: 800px;
+ overflow: scroll;
+ }
+
+ #scroller {
+ height: 2000px;
+ background: repeating-linear-gradient(#EEE, #EEE 100px, #DDD 100px, #DDD 200px);
+ }
+
+ #scrollbox {
+ margin-top: 200px;
+ width: 500px;
+ height: 500px;
+ border-radius: 250px;
+ box-shadow: inset 0 0 0 60px #555;
+ background: #777;
+ }
+
+ #circle {
+ position: relative;
+ left: 240px;
+ top: 20px;
+ border: 10px solid white;
+ border-radius: 10px;
+ width: 0px;
+ height: 0px;
+ transform-origin: 10px 230px;
+ will-change: transform;
+ }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1161206">Mozilla Bug 1161206</a>
+<p id="display"></p>
+<div id="content">
+ <p>Scrolling the page should be async, but scrolling over the dark circle should not scroll the page and instead rotate the white ball.</p>
+ <div id="scroller">
+ <div id="scrollbox">
+ <div id="circle"></div>
+ </div>
+ </div>
+</div>
+<pre id="test">
+<script type="application/javascript;version=1.7">
+
+var rotation = 0;
+var rotationAdjusted = false;
+
+var incrementForMode = function (mode) {
+ switch (mode) {
+ case WheelEvent.DOM_DELTA_PIXEL: return 1;
+ case WheelEvent.DOM_DELTA_LINE: return 15;
+ case WheelEvent.DOM_DELTA_PAGE: return 400;
+ }
+ return 0;
+};
+
+document.getElementById("scrollbox").addEventListener("wheel", function (e) {
+ rotation += e.deltaY * incrementForMode(e.deltaMode) * 0.2;
+ document.getElementById("circle").style.transform = "rotate(" + rotation + "deg)";
+ rotationAdjusted = true;
+ e.preventDefault();
+});
+
+function* test(testDriver) {
+ var content = document.getElementById('content');
+ for (i = 0; i < 300; i++) { // enough iterations that we would scroll to the bottom of 'content'
+ yield synthesizeNativeWheelAndWaitForWheelEvent(content, 100, 150, 0, -5, testDriver);
+ }
+ var scrollbox = document.getElementById('scrollbox');
+ is(content.scrollTop > 0, true, "We should have scrolled down somewhat");
+ is(content.scrollTop < content.scrollTopMax, true, "We should not have scrolled to the bottom of the scrollframe");
+ is(rotationAdjusted, true, "The rotation should have been adjusted");
+}
+
+SimpleTest.testInChaosMode();
+SimpleTest.waitForExplicitFinish();
+
+// If we allow smooth scrolling the "smooth" scrolling may cause the page to
+// glide past the scrollbox (which is supposed to stop the scrolling) and so
+// we might end up at the bottom of the page.
+pushPrefs([["general.smoothScroll", false]])
+.then(waitUntilApzStable)
+.then(runContinuation(test))
+.then(SimpleTest.finish);
+
+</script>
+</pre>
+
+</body>
+</html>
diff --git a/gfx/layers/apz/test/mochitest/test_wheel_transactions.html b/gfx/layers/apz/test/mochitest/test_wheel_transactions.html
new file mode 100644
index 000000000..e00e992cd
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_wheel_transactions.html
@@ -0,0 +1,137 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1175585
+-->
+<head>
+ <title>Test for Bug 1175585</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/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_native_event_utils.js"></script>
+ <script type="application/javascript" src="apz_test_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ #outer-frame {
+ height: 500px;
+ overflow: scroll;
+ background: repeating-linear-gradient(#CCC, #CCC 100px, #BBB 100px, #BBB 200px);
+ }
+ #inner-frame {
+ margin-top: 25%;
+ height: 200%;
+ width: 75%;
+ overflow: scroll;
+ }
+ #inner-content {
+ height: 200%;
+ width: 200%;
+ background: repeating-linear-gradient(#EEE, #EEE 100px, #DDD 100px, #DDD 200px);
+ }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1175585">APZ wheel transactions test</a>
+<p id="display"></p>
+<div id="outer-frame">
+ <div id="inner-frame">
+ <div id="inner-content"></div>
+ </div>
+</div>
+<pre id="test">
+<script type="application/javascript;version=1.7">
+
+function scrollWheelOver(element, deltaY, testDriver) {
+ synthesizeNativeWheelAndWaitForScrollEvent(element, 10, 10, 0, deltaY, testDriver);
+}
+
+function* test(testDriver) {
+ var outer = document.getElementById('outer-frame');
+ var inner = document.getElementById('inner-frame');
+ var innerContent = document.getElementById('inner-content');
+
+ // Register a wheel event listener that records the target of
+ // the last wheel event, so that we can make assertions about it.
+ var lastWheelTarget;
+ var wheelTargetRecorder = function(e) { lastWheelTarget = e.target; };
+ window.addEventListener("wheel", wheelTargetRecorder);
+
+ // Scroll |outer| to the bottom.
+ while (outer.scrollTop < outer.scrollTopMax) {
+ yield scrollWheelOver(outer, -10, testDriver);
+ }
+
+ // Verify that this has brought |inner| under the wheel.
+ is(lastWheelTarget, innerContent, "'inner-content' should have been brought under the wheel");
+ window.removeEventListener("wheel", wheelTargetRecorder);
+
+ // Immediately after, scroll it back up a bit.
+ yield scrollWheelOver(outer, 10, testDriver);
+
+ // Check that it was |outer| that scrolled back, and |inner| didn't
+ // scroll at all, as all the above scrolls should be in the same
+ // transaction.
+ ok(outer.scrollTop < outer.scrollTopMax, "'outer' should have scrolled back a bit");
+ is(inner.scrollTop, 0, "'inner' should not have scrolled");
+
+ // The next part of the test is related to the transaction timeout.
+ // Turn it down a bit so waiting for the timeout to elapse doesn't
+ // slow down the test harness too much.
+ var timeout = 5;
+ yield SpecialPowers.pushPrefEnv({"set": [["mousewheel.transaction.timeout", timeout]]}, testDriver);
+ SimpleTest.requestFlakyTimeout("we are testing code that measures actual elapsed time between two events");
+
+ // Scroll up a bit more. It's still |outer| scrolling because
+ // |inner| is still scrolled all the way to the top.
+ yield scrollWheelOver(outer, 10, testDriver);
+
+ // Wait for the transaction timeout to elapse.
+ // timeout * 5 is used to make it less likely that the timeout is less than
+ // the system timestamp resolution
+ yield window.setTimeout(testDriver, timeout * 5);
+
+ // Now scroll down. The transaction having timed out, the event
+ // should pick up a new target, and that should be |inner|.
+ yield scrollWheelOver(outer, -10, testDriver);
+ ok(inner.scrollTop > 0, "'inner' should have been scrolled");
+
+ // Finally, test scroll handoff after a timeout.
+
+ // Continue scrolling |inner| down to the bottom.
+ var prevScrollTop = inner.scrollTop;
+ while (inner.scrollTop < inner.scrollTopMax) {
+ yield scrollWheelOver(outer, -10, testDriver);
+ // Avoid a failure getting us into an infinite loop.
+ ok(inner.scrollTop > prevScrollTop, "scrolling down should increase scrollTop");
+ prevScrollTop = inner.scrollTop;
+ }
+
+ // Wait for the transaction timeout to elapse.
+ // timeout * 5 is used to make it less likely that the timeout is less than
+ // the system timestamp resolution
+ yield window.setTimeout(testDriver, timeout * 5);
+
+ // Continued downward scrolling should scroll |outer| to the bottom.
+ prevScrollTop = outer.scrollTop;
+ while (outer.scrollTop < outer.scrollTopMax) {
+ yield scrollWheelOver(outer, -10, testDriver);
+ // Avoid a failure getting us into an infinite loop.
+ ok(outer.scrollTop > prevScrollTop, "scrolling down should increase scrollTop");
+ prevScrollTop = outer.scrollTop;
+ }
+}
+
+SimpleTest.waitForExplicitFinish();
+
+// Disable smooth scrolling because it makes the test flaky (we don't have a good
+// way of detecting when the scrolling is finished).
+pushPrefs([["general.smoothScroll", false]])
+.then(waitUntilApzStable)
+.then(runContinuation(test))
+.then(SimpleTest.finish);
+
+</script>
+</pre>
+
+</body>
+</html>