зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1331390 - Don't over scroll when mouse down on scrollbar if smooth scroll is enabled. r=botond
Differential Revision: https://phabricator.services.mozilla.com/D171017
This commit is contained in:
Родитель
880a91aacf
Коммит
d45f60d317
|
@ -0,0 +1,86 @@
|
||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width; initial-scale=1.0">
|
||||||
|
<title>Scrolling with mouse down on the 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 src="/tests/SimpleTest/paint_listener.js"></script>
|
||||||
|
<style>
|
||||||
|
.content {
|
||||||
|
width: 1000px;
|
||||||
|
height: 20000px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
async function test() {
|
||||||
|
var targetElement = elementForTarget(window);
|
||||||
|
var w = {},
|
||||||
|
h = {};
|
||||||
|
utilsForTarget(window).getScrollbarSizes(targetElement, w, h);
|
||||||
|
var verticalScrollbarWidth = w.value;
|
||||||
|
var mouseX = targetElement.clientWidth + verticalScrollbarWidth / 2;
|
||||||
|
var mouseY = targetElement.clientHeight - 100; // 100 pixels above the bottom of the scrollbar track
|
||||||
|
|
||||||
|
let scrollEndPromise = promiseScrollend();
|
||||||
|
|
||||||
|
// Click and hold the mouse. Thumb should start scrolling towards the click location.
|
||||||
|
await promiseNativeMouseEventWithAPZ({
|
||||||
|
target: window,
|
||||||
|
offsetX: mouseX,
|
||||||
|
offsetY: mouseY,
|
||||||
|
type: "mousemove",
|
||||||
|
});
|
||||||
|
// mouse down
|
||||||
|
await promiseNativeMouseEventWithAPZ({
|
||||||
|
target: window,
|
||||||
|
offsetX: mouseX,
|
||||||
|
offsetY: mouseY,
|
||||||
|
type: "mousedown",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Wait for scrolling to complete
|
||||||
|
await scrollEndPromise;
|
||||||
|
|
||||||
|
// Work around bug 1825879: we may get an extra scrollend
|
||||||
|
// event too early.
|
||||||
|
if (window.scrollY < (window.scrollMaxY / 2)) {
|
||||||
|
scrollEndPromise = promiseScrollend();
|
||||||
|
await scrollEndPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush everything just to be safe
|
||||||
|
await promiseOnlyApzControllerFlushed();
|
||||||
|
|
||||||
|
var result = hitTest({x: mouseX, y: mouseY});
|
||||||
|
|
||||||
|
// Check that the scroll thumb is under the cursor.
|
||||||
|
// If the bug occurs, the thumb will scroll too far and
|
||||||
|
// will not be under the cursor.
|
||||||
|
ok((result.hitInfo & APZHitResultFlags.SCROLLBAR_THUMB) != 0, "Scrollbar thumb hit");
|
||||||
|
|
||||||
|
await promiseNativeMouseEventWithAPZ({
|
||||||
|
target: window,
|
||||||
|
offsetX: mouseX,
|
||||||
|
offsetY: mouseY,
|
||||||
|
type: "mouseup",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getPlatform() == "linux") {
|
||||||
|
ok(true, "Skipping test because the issue does not apply to Linux");
|
||||||
|
subtestDone();
|
||||||
|
} else {
|
||||||
|
waitUntilApzStable()
|
||||||
|
.then(test)
|
||||||
|
.then(subtestDone, subtestFailed);
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="content">Some content to ensure the root scrollframe is scrollable</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -56,6 +56,10 @@ var subtests = [
|
||||||
{"file": "helper_bug1662800.html"},
|
{"file": "helper_bug1662800.html"},
|
||||||
// Scrollbar-dragging on subframe with enclosing translation transform
|
// Scrollbar-dragging on subframe with enclosing translation transform
|
||||||
{"file": "helper_drag_bug1719913.html"},
|
{"file": "helper_drag_bug1719913.html"},
|
||||||
|
// Scrolling with mouse down on the scrollbar
|
||||||
|
{"file": "helper_scrollbartrack_click_overshoot.html",
|
||||||
|
"prefs": [["test.events.async.enabled", true], ["apz.test.logging_enabled", true],
|
||||||
|
["ui.useOverlayScrollbars", 0]]},
|
||||||
];
|
];
|
||||||
|
|
||||||
// Android, mac, and linux (at least in our automation) do not have scrollbar buttons.
|
// Android, mac, and linux (at least in our automation) do not have scrollbar buttons.
|
||||||
|
|
|
@ -79,7 +79,7 @@ nsSliderFrame::nsSliderFrame(ComputedStyle* aStyle, nsPresContext* aPresContext)
|
||||||
mDragStart(0),
|
mDragStart(0),
|
||||||
mThumbStart(0),
|
mThumbStart(0),
|
||||||
mCurPos(0),
|
mCurPos(0),
|
||||||
mChange(0),
|
mRepeatDirection(0),
|
||||||
mDragFinished(true),
|
mDragFinished(true),
|
||||||
mUserChanged(false),
|
mUserChanged(false),
|
||||||
mScrollingWithAPZ(false),
|
mScrollingWithAPZ(false),
|
||||||
|
@ -675,7 +675,7 @@ nsresult nsSliderFrame::HandleEvent(nsPresContext* aPresContext,
|
||||||
if (!GetEventPoint(aEvent, eventPoint)) {
|
if (!GetEventPoint(aEvent, eventPoint)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (mChange) {
|
if (mRepeatDirection) {
|
||||||
// On Linux the destination point is determined by the initial click
|
// On Linux the destination point is determined by the initial click
|
||||||
// on the scrollbar track and doesn't change until the mouse button
|
// on the scrollbar track and doesn't change until the mouse button
|
||||||
// is released.
|
// is released.
|
||||||
|
@ -807,8 +807,9 @@ nsresult nsSliderFrame::HandleEvent(nsPresContext* aPresContext,
|
||||||
// HandleRelease(aPresContext, aEvent, aEventStatus);
|
// HandleRelease(aPresContext, aEvent, aEventStatus);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
if (aEvent->mMessage == eMouseOut && mChange)
|
if (aEvent->mMessage == eMouseOut && mRepeatDirection) {
|
||||||
HandleRelease(aPresContext, aEvent, aEventStatus);
|
HandleRelease(aPresContext, aEvent, aEventStatus);
|
||||||
|
}
|
||||||
|
|
||||||
return nsIFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
|
return nsIFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
|
||||||
}
|
}
|
||||||
|
@ -1236,9 +1237,9 @@ nsresult nsSliderFrame::StopDrag() {
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (mChange) {
|
if (mRepeatDirection) {
|
||||||
StopRepeat();
|
StopRepeat();
|
||||||
mChange = 0;
|
mRepeatDirection = 0;
|
||||||
}
|
}
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
@ -1400,7 +1401,7 @@ nsSliderFrame::HandlePress(nsPresContext* aPresContext, WidgetGUIEvent* aEvent,
|
||||||
change = -1;
|
change = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
mChange = change;
|
mRepeatDirection = change;
|
||||||
DragThumb(true);
|
DragThumb(true);
|
||||||
// On Linux we want to keep scrolling in the direction indicated by |change|
|
// On Linux we want to keep scrolling in the direction indicated by |change|
|
||||||
// until the mouse is released. On the other platforms we want to stop
|
// until the mouse is released. On the other platforms we want to stop
|
||||||
|
@ -1418,7 +1419,7 @@ nsSliderFrame::HandlePress(nsPresContext* aPresContext, WidgetGUIEvent* aEvent,
|
||||||
mDestinationPoint = eventPoint;
|
mDestinationPoint = eventPoint;
|
||||||
#endif
|
#endif
|
||||||
StartRepeat();
|
StartRepeat();
|
||||||
PageScroll(change);
|
PageScroll(false);
|
||||||
|
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
@ -1464,13 +1465,13 @@ void nsSliderFrame::Notify() {
|
||||||
// See if the thumb has moved past our destination point.
|
// See if the thumb has moved past our destination point.
|
||||||
// if it has we want to stop.
|
// if it has we want to stop.
|
||||||
if (isHorizontal) {
|
if (isHorizontal) {
|
||||||
if (mChange < 0) {
|
if (mRepeatDirection < 0) {
|
||||||
if (thumbRect.x < mDestinationPoint.x) stop = true;
|
if (thumbRect.x < mDestinationPoint.x) stop = true;
|
||||||
} else {
|
} else {
|
||||||
if (thumbRect.x + thumbRect.width > mDestinationPoint.x) stop = true;
|
if (thumbRect.x + thumbRect.width > mDestinationPoint.x) stop = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (mChange < 0) {
|
if (mRepeatDirection < 0) {
|
||||||
if (thumbRect.y < mDestinationPoint.y) stop = true;
|
if (thumbRect.y < mDestinationPoint.y) stop = true;
|
||||||
} else {
|
} else {
|
||||||
if (thumbRect.y + thumbRect.height > mDestinationPoint.y) stop = true;
|
if (thumbRect.y + thumbRect.height > mDestinationPoint.y) stop = true;
|
||||||
|
@ -1480,24 +1481,78 @@ void nsSliderFrame::Notify() {
|
||||||
if (stop) {
|
if (stop) {
|
||||||
StopRepeat();
|
StopRepeat();
|
||||||
} else {
|
} else {
|
||||||
PageScroll(mChange);
|
PageScroll(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void nsSliderFrame::PageScroll(nscoord aChange) {
|
void nsSliderFrame::PageScroll(bool aClickAndHold) {
|
||||||
|
int32_t changeDirection = mRepeatDirection;
|
||||||
if (mContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::dir,
|
if (mContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::dir,
|
||||||
nsGkAtoms::reverse, eCaseMatters)) {
|
nsGkAtoms::reverse, eCaseMatters)) {
|
||||||
aChange = -aChange;
|
changeDirection = -changeDirection;
|
||||||
}
|
}
|
||||||
nsScrollbarFrame* sb = Scrollbar();
|
nsScrollbarFrame* sb = Scrollbar();
|
||||||
sb->SetIncrementToPage(aChange);
|
|
||||||
if (nsIScrollbarMediator* m = sb->GetScrollbarMediator()) {
|
nsIScrollableFrame* sf = GetScrollFrame();
|
||||||
m->ScrollByPage(sb, aChange,
|
const ScrollSnapFlags scrollSnapFlags =
|
||||||
ScrollSnapFlags::IntendedDirection |
|
ScrollSnapFlags::IntendedDirection | ScrollSnapFlags::IntendedEndPosition;
|
||||||
ScrollSnapFlags::IntendedEndPosition);
|
|
||||||
|
// If our nsIScrollbarMediator implementation is an nsIScrollableFrame,
|
||||||
|
// use ScrollTo() to ensure we do not scroll past the intended
|
||||||
|
// destination. Otherwise, the combination of smooth scrolling and
|
||||||
|
// ScrollBy() semantics (which adds the delta to the current destination
|
||||||
|
// if there is a smooth scroll in progress) can lead to scrolling too far
|
||||||
|
// (bug 1331390).
|
||||||
|
// Only do this when the page scroll is triggered by the repeat timer
|
||||||
|
// when the mouse is being held down. For multiple clicks in
|
||||||
|
// succession, we want to make sure we scroll by a full page for
|
||||||
|
// each click, so we use ScrollByPage().
|
||||||
|
if (aClickAndHold && sf) {
|
||||||
|
nscoord distance;
|
||||||
|
const bool isHorizontal = sb->IsHorizontal();
|
||||||
|
|
||||||
|
nsIFrame* thumbFrame = mFrames.FirstChild();
|
||||||
|
if (!thumbFrame) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
nsRect thumbRect = thumbFrame->GetRect();
|
||||||
|
|
||||||
|
if (isHorizontal) {
|
||||||
|
distance = mDestinationPoint.x - thumbRect.x - thumbRect.width / 2;
|
||||||
|
} else {
|
||||||
|
distance = mDestinationPoint.y - thumbRect.y - thumbRect.height / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert distance along scrollbar track to amount of scrolled content.
|
||||||
|
distance = distance / GetThumbRatio();
|
||||||
|
|
||||||
|
nsIContent* content = sb->GetContent();
|
||||||
|
const CSSIntCoord pageLength = GetPageIncrement(content);
|
||||||
|
|
||||||
|
nsPoint pos = sf->GetScrollPosition();
|
||||||
|
|
||||||
|
distance =
|
||||||
|
std::min(abs(distance), CSSPixel::ToAppUnits(CSSCoord(pageLength))) *
|
||||||
|
changeDirection;
|
||||||
|
|
||||||
|
if (isHorizontal) {
|
||||||
|
pos.x += distance;
|
||||||
|
} else {
|
||||||
|
pos.y += distance;
|
||||||
|
}
|
||||||
|
|
||||||
|
sf->ScrollTo(pos, ScrollMode::SmoothMsd, nullptr, scrollSnapFlags);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
PageUpDown(aChange);
|
|
||||||
|
sb->SetIncrementToPage(changeDirection);
|
||||||
|
if (nsIScrollbarMediator* m = sb->GetScrollbarMediator()) {
|
||||||
|
m->ScrollByPage(sb, changeDirection, scrollSnapFlags);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
PageUpDown(changeDirection);
|
||||||
}
|
}
|
||||||
|
|
||||||
float nsSliderFrame::GetThumbRatio() const {
|
float nsSliderFrame::GetThumbRatio() const {
|
||||||
|
|
|
@ -189,7 +189,7 @@ class nsSliderFrame final : public nsContainerFrame {
|
||||||
static void Notify(void* aData) {
|
static void Notify(void* aData) {
|
||||||
(static_cast<nsSliderFrame*>(aData))->Notify();
|
(static_cast<nsSliderFrame*>(aData))->Notify();
|
||||||
}
|
}
|
||||||
void PageScroll(nscoord aChange);
|
void PageScroll(bool aClickAndHold);
|
||||||
|
|
||||||
nsPoint mDestinationPoint;
|
nsPoint mDestinationPoint;
|
||||||
RefPtr<nsSliderMediator> mMediator;
|
RefPtr<nsSliderMediator> mMediator;
|
||||||
|
@ -201,7 +201,7 @@ class nsSliderFrame final : public nsContainerFrame {
|
||||||
|
|
||||||
int32_t mCurPos;
|
int32_t mCurPos;
|
||||||
|
|
||||||
nscoord mChange;
|
nscoord mRepeatDirection;
|
||||||
|
|
||||||
bool mDragFinished;
|
bool mDragFinished;
|
||||||
|
|
||||||
|
|
Загрузка…
Ссылка в новой задаче