diff options
Diffstat (limited to 'gfx/layers/apz/test/mochitest')
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> |