diff options
Diffstat (limited to 'gfx/layers/apz/test/mochitest/test_interrupted_reflow.html')
-rw-r--r-- | gfx/layers/apz/test/mochitest/test_interrupted_reflow.html | 719 |
1 files changed, 719 insertions, 0 deletions
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> |