haikuwebkit/ManualTests/programmatic-scroll-flicker...

121 lines
2.6 KiB
HTML
Raw Permalink Normal View History

Programmatic scrolling and content changes are not always synchronized https://bugs.webkit.org/show_bug.cgi?id=139245 rdar://problem/18833612 Reviewed by Anders Carlsson. Manual test that tries to sync layout with programmatic scrolling. * ManualTests/programmatic-scroll-flicker.html: Added. Source/WebCore: For programmatic scrolls, AsyncScrollingCoordinator::requestScrollPositionUpdate() calls updateScrollPositionAfterAsyncScroll(), then dispatches the requested scroll position to the scrolling thread. Once the scrolling thread commits, it calls back to the main thread via scheduleUpdateScrollPositionAfterAsyncScroll(), which schedules a second call to updateScrollPositionAfterAsyncScroll() on a timer. That's a problem, because some other scroll may have happened in the meantime; when the timer fires, it can sometimes restore a stale scroll position. Fix by bailing early from scheduleUpdateScrollPositionAfterAsyncScroll() for programmatic scrolls, since we know that requestScrollPositionUpdate() already did the updateScrollPositionAfterAsyncScroll(). Test: ManualTests/programmatic-scroll-flicker.html * page/FrameView.cpp: (WebCore::FrameView::reset): nullptr. (WebCore::FrameView::setScrollPosition): Ditto. (WebCore::FrameView::setWasScrolledByUser): Ditto. * page/scrolling/AsyncScrollingCoordinator.cpp: (WebCore::AsyncScrollingCoordinator::requestScrollPositionUpdate): Use a local variable for isProgrammaticScroll just to make sure we use the same value for the duration of this function. (WebCore::AsyncScrollingCoordinator::scheduleUpdateScrollPositionAfterAsyncScroll): Do nothing if this is a programmatic scroll. Canonical link: https://commits.webkit.org/157191@main git-svn-id: https://svn.webkit.org/repository/webkit/trunk@176899 268f45cc-cd09-0410-ab3c-d52691b4dbfc
2014-12-06 01:25:21 +00:00
<!DOCTYPE html>
<head>
<style>
#testInner {
position: absolute;
width: 600px;
top: 400px;
left: 200px;;
padding: 1em;
background: yellow;
box-shadow: 0 0 0.5em gray;
}
.marker {
position: fixed;
background: black;
color: white;
top: 200px;
padding: 1em;
}
.spacer {
height: 200px;
}
button {
position: fixed;
top: 100px;
left: 200px;
padding: 2em;
font-size: 16px;
font-weight: bold;
}
.trigger #testInner {
-webkit-transform: translateY(-200px);
}
</style>
</head>
<body>
<button onclick="toggleRunning()">
CLICK ME to start / stop test
</button>
<div class="marker">Correct top position</div>
<div id="parent" class="trigger">
<div id="testInner">test element</div>
</div>
<p class="spacer">.</p>
<p class="spacer">.</p>
<p class="spacer">.</p>
<p class="spacer">.</p>
<p class="spacer">.</p>
<p class="spacer">.</p>
<p class="spacer" id="last">.</p>
<script>
var INTERVAL_MS = 17;
var testElement = document.getElementById('testInner');
var testContainerElement = document.getElementById('parent');
var state = {};
state.triggerTranslationOnOrOff = false;
state.running = false;
state.intervalId = 0;
function updateState()
{
var translated = testContainerElement.classList.contains('trigger');
state.scrolled = !translated;
}
function toggleRunning()
{
state.running = !state.running;
if (state.running) {
updateState();
state.intervalId = setInterval(runSequence, INTERVAL_MS);
} else {
clearInterval(state.intervalId);
}
}
function doExpensiveContentUpdate()
{
var content = 'I should be stable at the correct position and not flicker above/below';
if (state.scrolled)
content += '.';
testElement.innerHTML = content;
var lastElement = document.getElementById('last');
var startTime = Date.now();
for (var i = 0; i < 1000; i++) {
lastElement.innerHTML = (lastElement.innerHTML + '.');
}
var domWriteTimeMs = Date.now() - startTime;
if (domWriteTimeMs > 2 * INTERVAL_MS)
lastElement.innerHTML = '';
}
function runSequence()
{
doExpensiveContentUpdate();
var newScrollTop;
if (state.scrolled) {
testContainerElement.classList.add('trigger');
newScrollTop = 0;
} else {
testContainerElement.classList.remove('trigger');
newScrollTop = 200;
}
document.body.scrollTop = newScrollTop;
state.scrolled = !state.scrolled;
}
</script>
</body>