Bug 422132 Store unused fractional scroll amount for later wheel events r=smaug

This commit is contained in:
Masayuki Nakano 2012-08-15 09:52:07 +09:00
parent dd064d015b
commit a2e9895dca
6 changed files with 201 additions and 54 deletions

View File

@ -2511,6 +2511,13 @@ nsEventStateManager::DispatchLegacyMouseScrollEvents(nsIFrame* aTargetFrame,
nsPresContext::AppUnitsToIntCSSPixels(scrollAmount.width),
nsPresContext::AppUnitsToIntCSSPixels(scrollAmount.height));
// XXX We don't deal with fractional amount in legacy event, though the
// default action handler (DoScrollText()) deals with it.
// If we implemented such strict computation, we would need additional
// accumulated delta values. It would made the code more complicated.
// And also it would compute different delta values from the older
// version. It doesn't make sense to implement such code for legacy
// events and rare cases.
PRInt32 scrollDeltaX, scrollDeltaY, pixelDeltaX, pixelDeltaY;
switch (aEvent->deltaMode) {
case nsIDOMWheelEvent::DOM_DELTA_PAGE:
@ -2729,26 +2736,23 @@ nsEventStateManager::ComputeScrollTarget(nsIFrame* aTargetFrame,
continue;
}
// Check if the scrollable view can be scrolled any further.
if (frameToScroll->GetLineScrollAmount().height) {
// For default action, we should climb up the tree if cannot scroll it
// by the event actually.
bool canScroll = CanScrollOn(frameToScroll,
aEvent->deltaX, aEvent->deltaY);
// Comboboxes need special care.
nsIComboboxControlFrame* comboBox = do_QueryFrame(scrollFrame);
if (comboBox) {
if (comboBox->IsDroppedDown()) {
// Don't propagate to parent when drop down menu is active.
return canScroll ? frameToScroll : nullptr;
}
// Always propagate when not dropped down (even if focused).
continue;
// For default action, we should climb up the tree if cannot scroll it
// by the event actually.
bool canScroll = CanScrollOn(frameToScroll,
aEvent->deltaX, aEvent->deltaY);
// Comboboxes need special care.
nsIComboboxControlFrame* comboBox = do_QueryFrame(scrollFrame);
if (comboBox) {
if (comboBox->IsDroppedDown()) {
// Don't propagate to parent when drop down menu is active.
return canScroll ? frameToScroll : nullptr;
}
// Always propagate when not dropped down (even if focused).
continue;
}
if (canScroll) {
return frameToScroll;
}
if (canScroll) {
return frameToScroll;
}
}
@ -2824,18 +2828,6 @@ nsEventStateManager::DoScrollText(nsIScrollableFrame* aScrollableFrame,
return;
}
// If the wheel event is line scroll and the delta value is computed from
// system settings, allow to override the system speed.
bool allowScrollSpeedOverride =
(!aEvent->customizedByUserPrefs &&
aEvent->deltaMode == nsIDOMWheelEvent::DOM_DELTA_LINE);
DeltaValues acceleratedDelta =
nsMouseWheelTransaction::AccelerateWheelDelta(aEvent,
allowScrollSpeedOverride);
bool isDeltaModePixel =
(aEvent->deltaMode == nsIDOMWheelEvent::DOM_DELTA_PIXEL);
// Default action's actual scroll amount should be computed from device
// pixels.
nsPresContext* pc = scrollFrame->PresContext();
@ -2843,17 +2835,9 @@ nsEventStateManager::DoScrollText(nsIScrollableFrame* aScrollableFrame,
nsIntSize scrollAmountInDevPixels(
pc->AppUnitsToDevPixels(scrollAmount.width),
pc->AppUnitsToDevPixels(scrollAmount.height));
nsIntPoint actualDevPixelScrollAmount(0, 0);
if (isDeltaModePixel) {
actualDevPixelScrollAmount.x = RoundDown(acceleratedDelta.deltaX);
actualDevPixelScrollAmount.y = RoundDown(acceleratedDelta.deltaY);
} else {
actualDevPixelScrollAmount.x =
RoundDown(scrollAmountInDevPixels.width * acceleratedDelta.deltaX);
actualDevPixelScrollAmount.y =
RoundDown(scrollAmountInDevPixels.height * acceleratedDelta.deltaY);
}
nsIntPoint actualDevPixelScrollAmount =
DeltaAccumulator::GetInstance()->
ComputeScrollAmountForDefaultAction(aEvent, scrollAmountInDevPixels);
nsIAtom* origin = nullptr;
switch (aEvent->deltaMode) {
@ -2887,6 +2871,9 @@ nsEventStateManager::DoScrollText(nsIScrollableFrame* aScrollableFrame,
-devPixelPageSize.height;
}
bool isDeltaModePixel =
(aEvent->deltaMode == nsIDOMWheelEvent::DOM_DELTA_PIXEL);
nsIScrollableFrame::ScrollMode mode;
switch (aEvent->scrollType) {
case widget::WheelEvent::SCROLL_DEFAULT:
@ -5081,13 +5068,6 @@ nsEventStateManager::DeltaAccumulator::InitLineOrPageDelta(
MOZ_ASSERT(aESM);
MOZ_ASSERT(aEvent);
if (!(aEvent->deltaMode == nsIDOMWheelEvent::DOM_DELTA_PIXEL &&
aEvent->isPixelOnlyDevice) &&
!WheelPrefs::GetInstance()->NeedToComputeLineOrPageDelta(aEvent)) {
Reset();
return;
}
// Reset if the previous wheel event is too old.
if (!mLastTime.IsNull()) {
TimeDuration duration = TimeStamp::Now() - mLastTime;
@ -5096,7 +5076,7 @@ nsEventStateManager::DeltaAccumulator::InitLineOrPageDelta(
}
}
// If we have accumulated delta, we may need to reset it.
if (mHandlingDeltaMode != PR_UINT32_MAX) {
if (IsInTransaction()) {
// If wheel event type is changed, reset the values.
if (mHandlingDeltaMode != aEvent->deltaMode ||
mHandlingPixelOnlyDevice != aEvent->isPixelOnlyDevice) {
@ -5116,6 +5096,17 @@ nsEventStateManager::DeltaAccumulator::InitLineOrPageDelta(
mHandlingDeltaMode = aEvent->deltaMode;
mHandlingPixelOnlyDevice = aEvent->isPixelOnlyDevice;
// If it's handling neither pixel scroll mode for pixel only device nor
// delta values multiplied by prefs, we must not modify lineOrPageDelta
// values.
if (!(mHandlingDeltaMode == nsIDOMWheelEvent::DOM_DELTA_PIXEL &&
mHandlingPixelOnlyDevice) &&
!nsEventStateManager::WheelPrefs::GetInstance()->
NeedToComputeLineOrPageDelta(aEvent)) {
mLastTime = TimeStamp::Now();
return;
}
mX += aEvent->deltaX;
mY += aEvent->deltaY;
@ -5156,10 +5147,45 @@ void
nsEventStateManager::DeltaAccumulator::Reset()
{
mX = mY = 0.0;
mPendingScrollAmountX = mPendingScrollAmountY = 0.0;
mHandlingDeltaMode = PR_UINT32_MAX;
mHandlingPixelOnlyDevice = false;
}
nsIntPoint
nsEventStateManager::DeltaAccumulator::ComputeScrollAmountForDefaultAction(
widget::WheelEvent* aEvent,
const nsIntSize& aScrollAmountInDevPixels)
{
MOZ_ASSERT(aEvent);
// If the wheel event is line scroll and the delta value is computed from
// system settings, allow to override the system speed.
bool allowScrollSpeedOverride =
(!aEvent->customizedByUserPrefs &&
aEvent->deltaMode == nsIDOMWheelEvent::DOM_DELTA_LINE);
DeltaValues acceleratedDelta =
nsMouseWheelTransaction::AccelerateWheelDelta(aEvent,
allowScrollSpeedOverride);
nsIntPoint result(0, 0);
if (aEvent->deltaMode == nsIDOMWheelEvent::DOM_DELTA_PIXEL) {
mPendingScrollAmountX += acceleratedDelta.deltaX;
mPendingScrollAmountY += acceleratedDelta.deltaY;
} else {
mPendingScrollAmountX +=
aScrollAmountInDevPixels.width * acceleratedDelta.deltaX;
mPendingScrollAmountY +=
aScrollAmountInDevPixels.height * acceleratedDelta.deltaY;
}
result.x = RoundDown(mPendingScrollAmountX);
result.y = RoundDown(mPendingScrollAmountY);
mPendingScrollAmountX -= result.x;
mPendingScrollAmountY -= result.y;
return result;
}
/******************************************************************/
/* nsEventStateManager::WheelPrefs */
/******************************************************************/

View File

@ -529,6 +529,8 @@ protected:
sInstance = nullptr;
}
bool IsInTransaction() { return mHandlingDeltaMode != PR_UINT32_MAX; }
/**
* InitLineOrPageDelta() stores pixel delta values of WheelEvents which are
* caused if it's needed. And if the accumulated delta becomes a
@ -539,19 +541,34 @@ protected:
mozilla::widget::WheelEvent* aEvent);
/**
* Reset() resets both delta values.
* Reset() resets all members.
*/
void Reset();
/**
* ComputeScrollAmountForDefaultAction() computes the default action's
* scroll amount in device pixels with mPendingScrollAmount*.
*/
nsIntPoint ComputeScrollAmountForDefaultAction(
mozilla::widget::WheelEvent* aEvent,
const nsIntSize& aScrollAmountInDevPixels);
private:
DeltaAccumulator() :
mX(0.0), mY(0.0), mHandlingDeltaMode(PR_UINT32_MAX),
mHandlingPixelOnlyDevice(false)
mX(0.0), mY(0.0), mPendingScrollAmountX(0.0), mPendingScrollAmountY(0.0),
mHandlingDeltaMode(PR_UINT32_MAX), mHandlingPixelOnlyDevice(false)
{
}
double mX;
double mY;
// When default action of a wheel event is scroll but some delta values
// are ignored because the computed amount values are not integer, the
// fractional values are saved by these members.
double mPendingScrollAmountX;
double mPendingScrollAmountY;
TimeStamp mLastTime;
PRUint32 mHandlingDeltaMode;

View File

@ -34,6 +34,7 @@ MOCHITEST_FILES = \
test_bug405632.html \
test_bug409604.html \
test_bug412567.html \
test_bug422132.html \
test_bug426082.html \
test_bug427537.html \
test_bug432698.html \

View File

@ -0,0 +1,92 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=422132
-->
<head>
<title>Test for Bug 422132</title>
<script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=422132">Mozilla Bug 422132</a>
<p id="display"></p>
<div id="target" style="font-size: 0; width: 200px; height: 200px; overflow: auto;">
<div style="width: 1000px; height: 1000px;"></div>
</div>
<div id="content" style="display: none">
</div>
<pre id="test">
<script class="testbody" type="text/javascript">
/** Test for Bug 422132 **/
SimpleTest.waitForExplicitFinish();
SimpleTest.waitForFocus(runTests, window);
function hitEventLoop(aFunc, aTimes)
{
if (--aTimes) {
setTimeout(hitEventLoop, 0, aFunc, aTimes);
} else {
setTimeout(aFunc, 20);
}
}
function runTests()
{
SpecialPowers.setIntPref("mousewheel.min_line_scroll_amount", 1);
var target = document.getElementById("target");
var scrollLeft = target.scrollLeft;
var scrollTop = target.scrollTop;
synthesizeWheel(target, 10, 10,
{ deltaMode: WheelEvent.DOM_DELTA_PIXEL,
deltaX: 0.5, deltaY: 0.5, lineOrPageDeltaX: 0, lineOrPageDeltaY: 0 });
hitEventLoop(function () {
is(target.scrollLeft, scrollLeft, "scrolled to right by 0.5px delta value");
is(target.scrollTop, scrollTop, "scrolled to bottom by 0.5px delta value");
scrollLeft = target.scrollLeft;
scrollTop = target.scrollTop;
synthesizeWheel(target, 10, 10,
{ deltaMode: WheelEvent.DOM_DELTA_PIXEL,
deltaX: 0.5, deltaY: 0.5, lineOrPageDeltaX: 0, lineOrPageDeltaY: 0 });
hitEventLoop(function () {
ok(target.scrollLeft > scrollLeft,
"not scrolled to right by 0.5px delta value with pending 0.5px delta");
ok(target.scrollTop > scrollTop,
"not scrolled to bottom by 0.5px delta value with pending 0.5px delta");
scrollLeft = target.scrollLeft;
scrollTop = target.scrollTop;
synthesizeWheel(target, 10, 10,
{ deltaMode: WheelEvent.DOM_DELTA_LINE,
deltaX: 0.5, deltaY: 0.5, lineOrPageDeltaX: 0, lineOrPageDeltaY: 0 });
hitEventLoop(function () {
is(target.scrollLeft, scrollLeft, "scrolled to right by 0.5 line delta value");
is(target.scrollTop, scrollTop, "scrolled to bottom by 0.5 line delta value");
scrollLeft = target.scrollLeft;
scrollTop = target.scrollTop;
synthesizeWheel(target, 10, 10,
{ deltaMode: WheelEvent.DOM_DELTA_LINE,
deltaX: 0.5, deltaY: 0.5, lineOrPageDeltaX: 1, lineOrPageDeltaY: 1 });
hitEventLoop(function () {
ok(target.scrollLeft > scrollLeft,
"not scrolled to right by 0.5 line delta value with pending 0.5 line delta");
ok(target.scrollTop > scrollTop,
"not scrolled to bottom by 0.5 line delta value with pending 0.5 line delta");
SpecialPowers.clearUserPref("mousewheel.min_line_scroll_amount");
SimpleTest.finish();
}, 20);
}, 20);
}, 20);
}, 20);
}
</script>
</pre>
</body>
</html>

View File

@ -2548,9 +2548,16 @@ nsGfxScrollFrameInner::GetLineScrollAmount() const
nsLayoutUtils::GetFontMetricsForFrame(mOuter, getter_AddRefs(fm),
nsLayoutUtils::FontSizeInflationFor(mOuter));
NS_ASSERTION(fm, "FontMetrics is null, assuming fontHeight == 1 appunit");
nscoord fontHeight = 1;
static nscoord sMinLineScrollAmountInPixels = -1;
if (sMinLineScrollAmountInPixels < 0) {
Preferences::AddIntVarCache(&sMinLineScrollAmountInPixels,
"mousewheel.min_line_scroll_amount", 1);
}
PRUint32 appUnitsPerDevPixel = mOuter->PresContext()->AppUnitsPerDevPixel();
nscoord fontHeight =
NS_MAX(1, sMinLineScrollAmountInPixels) * appUnitsPerDevPixel;
if (fm) {
fontHeight = fm->MaxHeight();
fontHeight = NS_MAX(fm->MaxHeight(), fontHeight);
}
return nsSize(fontHeight, fontHeight);

View File

@ -1417,6 +1417,10 @@ pref("mousewheel.with_win.delta_multiplier_x", 100);
pref("mousewheel.with_win.delta_multiplier_y", 100);
pref("mousewheel.with_win.delta_multiplier_z", 100);
// If line-height is lower than this value (in device pixels), 1 line scroll
// scrolls this height.
pref("mousewheel.min_line_scroll_amount", 5);
// These define the smooth scroll behavior (min ms, max ms) for different triggers
// Some triggers:
// mouseWheel: Discrete mouse wheel events, Synaptics touchpads on windows (generate wheel events)