forked from mirrors/gecko-dev
Bug 1168182 - Bind wheel event targets to wheel transactions. r=masayuki,smaug
- Create wheel transactions for wheel events handled by APZ. - Group wheel events with the current wheel transaction, so that all wheel events in a wheel transaction are fired to the same element. - Store the current event target for the first event in a wheel transaction to be used for subsequent events. - Add the dom.event.wheel-event-groups.enabled preference as a feature flag for this behavior. Differential Revision: https://phabricator.services.mozilla.com/D163484
This commit is contained in:
parent
17db57410f
commit
30e2548477
11 changed files with 219 additions and 55 deletions
|
|
@ -2745,7 +2745,7 @@ nsIFrame* EventStateManager::ComputeScrollTargetAndMayAdjustWheelEvent(
|
||||||
// out of the frame, or when more than "mousewheel.transaction.timeout"
|
// out of the frame, or when more than "mousewheel.transaction.timeout"
|
||||||
// milliseconds have passed after the last operation, even if the mouse
|
// milliseconds have passed after the last operation, even if the mouse
|
||||||
// hasn't moved.
|
// hasn't moved.
|
||||||
nsIFrame* lastScrollFrame = WheelTransaction::GetTargetFrame();
|
nsIFrame* lastScrollFrame = WheelTransaction::GetScrollTargetFrame();
|
||||||
if (lastScrollFrame) {
|
if (lastScrollFrame) {
|
||||||
nsIScrollableFrame* scrollableFrame =
|
nsIScrollableFrame* scrollableFrame =
|
||||||
lastScrollFrame->GetScrollTargetFrame();
|
lastScrollFrame->GetScrollTargetFrame();
|
||||||
|
|
@ -2927,7 +2927,9 @@ void EventStateManager::DoScrollText(nsIScrollableFrame* aScrollableFrame,
|
||||||
MOZ_ASSERT(scrollFrame);
|
MOZ_ASSERT(scrollFrame);
|
||||||
|
|
||||||
AutoWeakFrame scrollFrameWeak(scrollFrame);
|
AutoWeakFrame scrollFrameWeak(scrollFrame);
|
||||||
if (!WheelTransaction::WillHandleDefaultAction(aEvent, scrollFrameWeak)) {
|
AutoWeakFrame eventFrameWeak(mCurrentTarget);
|
||||||
|
if (!WheelTransaction::WillHandleDefaultAction(aEvent, scrollFrameWeak,
|
||||||
|
eventFrameWeak)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3718,6 +3720,14 @@ nsresult EventStateManager::PostHandleEvent(nsPresContext* aPresContext,
|
||||||
case WheelPrefs::ACTION_NONE:
|
case WheelPrefs::ACTION_NONE:
|
||||||
default:
|
default:
|
||||||
bool allDeltaOverflown = false;
|
bool allDeltaOverflown = false;
|
||||||
|
if (wheelEvent->mDeltaX != 0.0 || wheelEvent->mDeltaY != 0.0) {
|
||||||
|
if (frameToScroll) {
|
||||||
|
WheelTransaction::WillHandleDefaultAction(
|
||||||
|
wheelEvent, frameToScroll, mCurrentTarget);
|
||||||
|
} else {
|
||||||
|
WheelTransaction::EndTransaction();
|
||||||
|
}
|
||||||
|
}
|
||||||
if (wheelEvent->mFlags.mHandledByAPZ) {
|
if (wheelEvent->mFlags.mHandledByAPZ) {
|
||||||
if (wheelEvent->mCanTriggerSwipe) {
|
if (wheelEvent->mCanTriggerSwipe) {
|
||||||
// For events that can trigger swipes, APZ needs to know whether
|
// For events that can trigger swipes, APZ needs to know whether
|
||||||
|
|
@ -5859,6 +5869,7 @@ void EventStateManager::ContentRemoved(Document* aDocument,
|
||||||
IMEStateManager::OnRemoveContent(*presContext,
|
IMEStateManager::OnRemoveContent(*presContext,
|
||||||
MOZ_KnownLive(*aContent->AsElement()));
|
MOZ_KnownLive(*aContent->AsElement()));
|
||||||
}
|
}
|
||||||
|
WheelTransaction::OnRemoveElement(aContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
// inform the focus manager that the content is being removed. If this
|
// inform the focus manager that the content is being removed. If this
|
||||||
|
|
|
||||||
|
|
@ -111,7 +111,8 @@ WheelHandlingUtils::GetDisregardedWheelScrollDirection(const nsIFrame* aFrame) {
|
||||||
/* mozilla::WheelTransaction */
|
/* mozilla::WheelTransaction */
|
||||||
/******************************************************************/
|
/******************************************************************/
|
||||||
|
|
||||||
AutoWeakFrame WheelTransaction::sTargetFrame(nullptr);
|
AutoWeakFrame WheelTransaction::sScrollTargetFrame(nullptr);
|
||||||
|
AutoWeakFrame WheelTransaction::sEventTargetFrame(nullptr);
|
||||||
uint32_t WheelTransaction::sTime = 0;
|
uint32_t WheelTransaction::sTime = 0;
|
||||||
uint32_t WheelTransaction::sMouseMoved = 0;
|
uint32_t WheelTransaction::sMouseMoved = 0;
|
||||||
nsITimer* WheelTransaction::sTimer = nullptr;
|
nsITimer* WheelTransaction::sTimer = nullptr;
|
||||||
|
|
@ -128,13 +129,28 @@ bool WheelTransaction::OutOfTime(uint32_t aBaseTime, uint32_t aThreshold) {
|
||||||
void WheelTransaction::OwnScrollbars(bool aOwn) { sOwnScrollbars = aOwn; }
|
void WheelTransaction::OwnScrollbars(bool aOwn) { sOwnScrollbars = aOwn; }
|
||||||
|
|
||||||
/* static */
|
/* static */
|
||||||
void WheelTransaction::BeginTransaction(nsIFrame* aTargetFrame,
|
void WheelTransaction::BeginTransaction(nsIFrame* aScrollTargetFrame,
|
||||||
|
nsIFrame* aEventTargetFrame,
|
||||||
const WidgetWheelEvent* aEvent) {
|
const WidgetWheelEvent* aEvent) {
|
||||||
NS_ASSERTION(!sTargetFrame, "previous transaction is not finished!");
|
NS_ASSERTION(!sScrollTargetFrame && !sEventTargetFrame,
|
||||||
|
"previous transaction is not finished!");
|
||||||
MOZ_ASSERT(aEvent->mMessage == eWheel,
|
MOZ_ASSERT(aEvent->mMessage == eWheel,
|
||||||
"Transaction must be started with a wheel event");
|
"Transaction must be started with a wheel event");
|
||||||
|
|
||||||
ScrollbarsForWheel::OwnWheelTransaction(false);
|
ScrollbarsForWheel::OwnWheelTransaction(false);
|
||||||
sTargetFrame = aTargetFrame;
|
sScrollTargetFrame = aScrollTargetFrame;
|
||||||
|
|
||||||
|
// Only set the static event target if wheel event groups are enabled.
|
||||||
|
if (StaticPrefs::dom_event_wheel_event_groups_enabled()) {
|
||||||
|
// Set a static event target for the wheel transaction. This will be used
|
||||||
|
// to override the event target frame when computing the event target from
|
||||||
|
// input coordinates. When this preference is not set or there is no stored
|
||||||
|
// event target for the current wheel transaction, the event target will
|
||||||
|
// not be overridden by the current wheel transaction, but will be computed
|
||||||
|
// from the input coordinates.
|
||||||
|
sEventTargetFrame = aEventTargetFrame;
|
||||||
|
}
|
||||||
|
|
||||||
sScrollSeriesCounter = 0;
|
sScrollSeriesCounter = 0;
|
||||||
if (!UpdateTransaction(aEvent)) {
|
if (!UpdateTransaction(aEvent)) {
|
||||||
NS_ERROR("BeginTransaction is called even cannot scroll the frame");
|
NS_ERROR("BeginTransaction is called even cannot scroll the frame");
|
||||||
|
|
@ -144,7 +160,7 @@ void WheelTransaction::BeginTransaction(nsIFrame* aTargetFrame,
|
||||||
|
|
||||||
/* static */
|
/* static */
|
||||||
bool WheelTransaction::UpdateTransaction(const WidgetWheelEvent* aEvent) {
|
bool WheelTransaction::UpdateTransaction(const WidgetWheelEvent* aEvent) {
|
||||||
nsIFrame* scrollToFrame = GetTargetFrame();
|
nsIFrame* scrollToFrame = GetScrollTargetFrame();
|
||||||
nsIScrollableFrame* scrollableFrame = scrollToFrame->GetScrollTargetFrame();
|
nsIScrollableFrame* scrollableFrame = scrollToFrame->GetScrollTargetFrame();
|
||||||
if (scrollableFrame) {
|
if (scrollableFrame) {
|
||||||
scrollToFrame = do_QueryFrame(scrollableFrame);
|
scrollToFrame = do_QueryFrame(scrollableFrame);
|
||||||
|
|
@ -188,7 +204,8 @@ void WheelTransaction::EndTransaction() {
|
||||||
if (sTimer) {
|
if (sTimer) {
|
||||||
sTimer->Cancel();
|
sTimer->Cancel();
|
||||||
}
|
}
|
||||||
sTargetFrame = nullptr;
|
sScrollTargetFrame = nullptr;
|
||||||
|
sEventTargetFrame = nullptr;
|
||||||
sScrollSeriesCounter = 0;
|
sScrollSeriesCounter = 0;
|
||||||
if (sOwnScrollbars) {
|
if (sOwnScrollbars) {
|
||||||
sOwnScrollbars = false;
|
sOwnScrollbars = false;
|
||||||
|
|
@ -199,13 +216,16 @@ void WheelTransaction::EndTransaction() {
|
||||||
|
|
||||||
/* static */
|
/* static */
|
||||||
bool WheelTransaction::WillHandleDefaultAction(
|
bool WheelTransaction::WillHandleDefaultAction(
|
||||||
WidgetWheelEvent* aWheelEvent, AutoWeakFrame& aTargetWeakFrame) {
|
WidgetWheelEvent* aWheelEvent, AutoWeakFrame& aScrollTargetWeakFrame,
|
||||||
nsIFrame* lastTargetFrame = GetTargetFrame();
|
AutoWeakFrame& aEventTargetWeakFrame) {
|
||||||
|
nsIFrame* lastTargetFrame = GetScrollTargetFrame();
|
||||||
if (!lastTargetFrame) {
|
if (!lastTargetFrame) {
|
||||||
BeginTransaction(aTargetWeakFrame.GetFrame(), aWheelEvent);
|
BeginTransaction(aScrollTargetWeakFrame.GetFrame(),
|
||||||
} else if (lastTargetFrame != aTargetWeakFrame.GetFrame()) {
|
aEventTargetWeakFrame.GetFrame(), aWheelEvent);
|
||||||
|
} else if (lastTargetFrame != aScrollTargetWeakFrame.GetFrame()) {
|
||||||
EndTransaction();
|
EndTransaction();
|
||||||
BeginTransaction(aTargetWeakFrame.GetFrame(), aWheelEvent);
|
BeginTransaction(aScrollTargetWeakFrame.GetFrame(),
|
||||||
|
aEventTargetWeakFrame.GetFrame(), aWheelEvent);
|
||||||
} else {
|
} else {
|
||||||
UpdateTransaction(aWheelEvent);
|
UpdateTransaction(aWheelEvent);
|
||||||
}
|
}
|
||||||
|
|
@ -214,7 +234,7 @@ bool WheelTransaction::WillHandleDefaultAction(
|
||||||
// UpdateTransaction() fires MozMouseScrollFailed event which is for
|
// UpdateTransaction() fires MozMouseScrollFailed event which is for
|
||||||
// automated testing. In the event handler, the target frame might be
|
// automated testing. In the event handler, the target frame might be
|
||||||
// destroyed. Then, the caller shouldn't try to handle the default action.
|
// destroyed. Then, the caller shouldn't try to handle the default action.
|
||||||
if (!aTargetWeakFrame.IsAlive()) {
|
if (!aScrollTargetWeakFrame.IsAlive()) {
|
||||||
EndTransaction();
|
EndTransaction();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -224,7 +244,7 @@ bool WheelTransaction::WillHandleDefaultAction(
|
||||||
|
|
||||||
/* static */
|
/* static */
|
||||||
void WheelTransaction::OnEvent(WidgetEvent* aEvent) {
|
void WheelTransaction::OnEvent(WidgetEvent* aEvent) {
|
||||||
if (!sTargetFrame) {
|
if (!sScrollTargetFrame) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -255,13 +275,17 @@ void WheelTransaction::OnEvent(WidgetEvent* aEvent) {
|
||||||
// terminate the scrollwheel transaction.
|
// terminate the scrollwheel transaction.
|
||||||
LayoutDeviceIntPoint pt = GetScreenPoint(mouseEvent);
|
LayoutDeviceIntPoint pt = GetScreenPoint(mouseEvent);
|
||||||
auto r = LayoutDeviceIntRect::FromAppUnitsToNearest(
|
auto r = LayoutDeviceIntRect::FromAppUnitsToNearest(
|
||||||
sTargetFrame->GetScreenRectInAppUnits(),
|
sScrollTargetFrame->GetScreenRectInAppUnits(),
|
||||||
sTargetFrame->PresContext()->AppUnitsPerDevPixel());
|
sScrollTargetFrame->PresContext()->AppUnitsPerDevPixel());
|
||||||
if (!r.Contains(pt)) {
|
if (!r.Contains(pt)) {
|
||||||
EndTransaction();
|
EndTransaction();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For mouse move events where the wheel transaction is still valid, the
|
||||||
|
// stored event target should be reset.
|
||||||
|
sEventTargetFrame = nullptr;
|
||||||
|
|
||||||
// If the cursor is moving inside the frame, and it is less than
|
// If the cursor is moving inside the frame, and it is less than
|
||||||
// ignoremovedelay milliseconds since the last scroll operation, ignore
|
// ignoremovedelay milliseconds since the last scroll operation, ignore
|
||||||
// the mouse move; otherwise, record the current mouse move time to be
|
// the mouse move; otherwise, record the current mouse move time to be
|
||||||
|
|
@ -291,35 +315,55 @@ void WheelTransaction::OnEvent(WidgetEvent* aEvent) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* static */
|
||||||
|
void WheelTransaction::OnRemoveElement(nsIContent* aContent) {
|
||||||
|
// If dom.event.wheel-event-groups.enabled is not set or we have no current
|
||||||
|
// wheel event transaction there is no internal state to be updated.
|
||||||
|
if (!sEventTargetFrame) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sEventTargetFrame->GetContent() == aContent) {
|
||||||
|
// Only invalidate the wheel transaction event target frame when the
|
||||||
|
// remove target is the event target of the wheel event group. The
|
||||||
|
// scroll target frame of the wheel event group may still be valid.
|
||||||
|
//
|
||||||
|
// With the stored event target unset, the target for any following
|
||||||
|
// events will be the frame found using the input coordinates.
|
||||||
|
sEventTargetFrame = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* static */
|
/* static */
|
||||||
void WheelTransaction::Shutdown() { NS_IF_RELEASE(sTimer); }
|
void WheelTransaction::Shutdown() { NS_IF_RELEASE(sTimer); }
|
||||||
|
|
||||||
/* static */
|
/* static */
|
||||||
void WheelTransaction::OnFailToScrollTarget() {
|
void WheelTransaction::OnFailToScrollTarget() {
|
||||||
MOZ_ASSERT(sTargetFrame, "We don't have mouse scrolling transaction");
|
MOZ_ASSERT(sScrollTargetFrame, "We don't have mouse scrolling transaction");
|
||||||
|
|
||||||
if (StaticPrefs::test_mousescroll()) {
|
if (StaticPrefs::test_mousescroll()) {
|
||||||
// This event is used for automated tests, see bug 442774.
|
// This event is used for automated tests, see bug 442774.
|
||||||
nsContentUtils::DispatchEventOnlyToChrome(
|
nsContentUtils::DispatchEventOnlyToChrome(
|
||||||
sTargetFrame->GetContent()->OwnerDoc(), sTargetFrame->GetContent(),
|
sScrollTargetFrame->GetContent()->OwnerDoc(),
|
||||||
u"MozMouseScrollFailed"_ns, CanBubble::eYes, Cancelable::eYes);
|
sScrollTargetFrame->GetContent(), u"MozMouseScrollFailed"_ns,
|
||||||
|
CanBubble::eYes, Cancelable::eYes);
|
||||||
}
|
}
|
||||||
// The target frame might be destroyed in the event handler, at that time,
|
// The target frame might be destroyed in the event handler, at that time,
|
||||||
// we need to finish the current transaction
|
// we need to finish the current transaction
|
||||||
if (!sTargetFrame) {
|
if (!sScrollTargetFrame) {
|
||||||
EndTransaction();
|
EndTransaction();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* static */
|
/* static */
|
||||||
void WheelTransaction::OnTimeout(nsITimer* aTimer, void* aClosure) {
|
void WheelTransaction::OnTimeout(nsITimer* aTimer, void* aClosure) {
|
||||||
if (!sTargetFrame) {
|
if (!sScrollTargetFrame) {
|
||||||
// The transaction target was destroyed already
|
// The transaction target was destroyed already
|
||||||
EndTransaction();
|
EndTransaction();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Store the sTargetFrame, the variable becomes null in EndTransaction.
|
// Store the sScrollTargetFrame, the variable becomes null in EndTransaction.
|
||||||
nsIFrame* frame = sTargetFrame;
|
nsIFrame* frame = sScrollTargetFrame;
|
||||||
// We need to finish current transaction before DOM event firing. Because
|
// We need to finish current transaction before DOM event firing. Because
|
||||||
// the next DOM event might create strange situation for us.
|
// the next DOM event might create strange situation for us.
|
||||||
MayEndTransaction();
|
MayEndTransaction();
|
||||||
|
|
@ -388,7 +432,7 @@ double WheelTransaction::ComputeAcceleratedWheelDelta(double aDelta,
|
||||||
/* static */
|
/* static */
|
||||||
DeltaValues WheelTransaction::OverrideSystemScrollSpeed(
|
DeltaValues WheelTransaction::OverrideSystemScrollSpeed(
|
||||||
WidgetWheelEvent* aEvent) {
|
WidgetWheelEvent* aEvent) {
|
||||||
MOZ_ASSERT(sTargetFrame, "We don't have mouse scrolling transaction");
|
MOZ_ASSERT(sScrollTargetFrame, "We don't have mouse scrolling transaction");
|
||||||
|
|
||||||
// If the event doesn't scroll to both X and Y, we don't need to do anything
|
// If the event doesn't scroll to both X and Y, we don't need to do anything
|
||||||
// here.
|
// here.
|
||||||
|
|
@ -446,7 +490,7 @@ void ScrollbarsForWheel::SetActiveScrollTarget(
|
||||||
|
|
||||||
/* static */
|
/* static */
|
||||||
void ScrollbarsForWheel::MayInactivate() {
|
void ScrollbarsForWheel::MayInactivate() {
|
||||||
if (!sOwnWheelTransaction && WheelTransaction::GetTargetFrame()) {
|
if (!sOwnWheelTransaction && WheelTransaction::GetScrollTargetFrame()) {
|
||||||
WheelTransaction::OwnScrollbars(true);
|
WheelTransaction::OwnScrollbars(true);
|
||||||
} else {
|
} else {
|
||||||
Inactivate();
|
Inactivate();
|
||||||
|
|
|
||||||
|
|
@ -118,23 +118,40 @@ class ScrollbarsForWheel {
|
||||||
|
|
||||||
class WheelTransaction {
|
class WheelTransaction {
|
||||||
public:
|
public:
|
||||||
static nsIFrame* GetTargetFrame() { return sTargetFrame; }
|
/**
|
||||||
|
* Get the target scroll frame for this wheel transaction. This should
|
||||||
|
* the the scrollable fame that will scroll for all wheel events in
|
||||||
|
* this wheel transaction.
|
||||||
|
*/
|
||||||
|
static nsIFrame* GetScrollTargetFrame() { return sScrollTargetFrame; }
|
||||||
|
/*
|
||||||
|
* The event target to use for all wheel events in this wheel transaction.
|
||||||
|
* This should be the event target for all wheel events in this wheel
|
||||||
|
* transaction. Note that this frame will likely be a child of the
|
||||||
|
* scrollable frame.
|
||||||
|
*/
|
||||||
|
static nsIFrame* GetEventTargetFrame() { return sEventTargetFrame; }
|
||||||
static void EndTransaction();
|
static void EndTransaction();
|
||||||
/**
|
/**
|
||||||
* WillHandleDefaultAction() is called before handling aWheelEvent on
|
* WillHandleDefaultAction() is called before handling aWheelEvent on
|
||||||
* aTargetFrame.
|
* aScrollTargetWeakFrame given the event target aEventTargetWeakFrame.
|
||||||
*
|
*
|
||||||
* @return false if the caller cannot continue to handle the default
|
* @return false if the caller cannot continue to handle the default
|
||||||
* action. Otherwise, true.
|
* action. Otherwise, true.
|
||||||
*/
|
*/
|
||||||
static bool WillHandleDefaultAction(WidgetWheelEvent* aWheelEvent,
|
static bool WillHandleDefaultAction(WidgetWheelEvent* aWheelEvent,
|
||||||
AutoWeakFrame& aTargetWeakFrame);
|
AutoWeakFrame& aScrollTargetWeakFrame,
|
||||||
|
AutoWeakFrame& aEventTargetWeakFrame);
|
||||||
static bool WillHandleDefaultAction(WidgetWheelEvent* aWheelEvent,
|
static bool WillHandleDefaultAction(WidgetWheelEvent* aWheelEvent,
|
||||||
nsIFrame* aTargetFrame) {
|
nsIFrame* aScrollTargetFrame,
|
||||||
AutoWeakFrame targetWeakFrame(aTargetFrame);
|
nsIFrame* aEventTargetFrame) {
|
||||||
return WillHandleDefaultAction(aWheelEvent, targetWeakFrame);
|
AutoWeakFrame scrollTargetWeakFrame(aScrollTargetFrame);
|
||||||
|
AutoWeakFrame eventTargetWeakFrame(aEventTargetFrame);
|
||||||
|
return WillHandleDefaultAction(aWheelEvent, scrollTargetWeakFrame,
|
||||||
|
eventTargetWeakFrame);
|
||||||
}
|
}
|
||||||
static void OnEvent(WidgetEvent* aEvent);
|
static void OnEvent(WidgetEvent* aEvent);
|
||||||
|
static void OnRemoveElement(nsIContent* aContent);
|
||||||
static void Shutdown();
|
static void Shutdown();
|
||||||
|
|
||||||
static void OwnScrollbars(bool aOwn);
|
static void OwnScrollbars(bool aOwn);
|
||||||
|
|
@ -142,7 +159,8 @@ class WheelTransaction {
|
||||||
static DeltaValues AccelerateWheelDelta(WidgetWheelEvent* aEvent);
|
static DeltaValues AccelerateWheelDelta(WidgetWheelEvent* aEvent);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
static void BeginTransaction(nsIFrame* aTargetFrame,
|
static void BeginTransaction(nsIFrame* aScrollTargetFrame,
|
||||||
|
nsIFrame* aEventTargetFrame,
|
||||||
const WidgetWheelEvent* aEvent);
|
const WidgetWheelEvent* aEvent);
|
||||||
// Be careful, UpdateTransaction may fire a DOM event, therefore, the target
|
// Be careful, UpdateTransaction may fire a DOM event, therefore, the target
|
||||||
// frame might be destroyed in the event handler.
|
// frame might be destroyed in the event handler.
|
||||||
|
|
@ -157,7 +175,23 @@ class WheelTransaction {
|
||||||
static double ComputeAcceleratedWheelDelta(double aDelta, int32_t aFactor);
|
static double ComputeAcceleratedWheelDelta(double aDelta, int32_t aFactor);
|
||||||
static bool OutOfTime(uint32_t aBaseTime, uint32_t aThreshold);
|
static bool OutOfTime(uint32_t aBaseTime, uint32_t aThreshold);
|
||||||
|
|
||||||
static AutoWeakFrame sTargetFrame;
|
/**
|
||||||
|
* The scrollable element the current wheel event group is bound to.
|
||||||
|
*/
|
||||||
|
static AutoWeakFrame sScrollTargetFrame;
|
||||||
|
/**
|
||||||
|
* The initial target of the first wheel event in the wheel event group.
|
||||||
|
* This frame is typically a child of the scrollable element. The wheel
|
||||||
|
* event should target the topmost-event-target. For a wheel event
|
||||||
|
* group, we'll use this target for the entire group.
|
||||||
|
*
|
||||||
|
* See https://w3c.github.io/uievents/#topmost-event-target and
|
||||||
|
* https://w3c.github.io/uievents/#event-type-wheel for details.
|
||||||
|
*
|
||||||
|
* Note: this is only populated if dom.event.wheel-event-groups.enabled is
|
||||||
|
* set.
|
||||||
|
*/
|
||||||
|
static AutoWeakFrame sEventTargetFrame;
|
||||||
static uint32_t sTime; // in milliseconds
|
static uint32_t sTime; // in milliseconds
|
||||||
static uint32_t sMouseMoved; // in milliseconds
|
static uint32_t sMouseMoved; // in milliseconds
|
||||||
static nsITimer* sTimer;
|
static nsITimer* sTimer;
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,10 @@
|
||||||
<html>
|
<html>
|
||||||
<!--
|
<!--
|
||||||
https://bugzilla.mozilla.org/show_bug.cgi?id=1013412
|
https://bugzilla.mozilla.org/show_bug.cgi?id=1013412
|
||||||
|
https://bugzilla.mozilla.org/show_bug.cgi?id=1168182
|
||||||
-->
|
-->
|
||||||
<head>
|
<head>
|
||||||
<title>Test for Bug 1013412</title>
|
<title>Test for Bug 1013412 and 1168182</title>
|
||||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||||
<script src="/tests/SimpleTest/paint_listener.js"></script>
|
<script src="/tests/SimpleTest/paint_listener.js"></script>
|
||||||
<script src="/tests/SimpleTest/EventUtils.js"></script>
|
<script src="/tests/SimpleTest/EventUtils.js"></script>
|
||||||
|
|
@ -44,9 +45,10 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1013412
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1013412">Mozilla Bug 1013412</a>
|
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1013412">Mozilla Bug 1013412</a>
|
||||||
|
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1168182">Mozilla Bug 1168182</a>
|
||||||
<p id="display"></p>
|
<p id="display"></p>
|
||||||
<div id="content">
|
<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>
|
<p>Scrolling the page should be async and scrolling over the dark circle should scroll the page and avoid rotating the white ball.</p>
|
||||||
<div id="scroller">
|
<div id="scroller">
|
||||||
<div id="scrollbox">
|
<div id="scrollbox">
|
||||||
<div id="circle"></div>
|
<div id="circle"></div>
|
||||||
|
|
@ -56,8 +58,6 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1013412
|
||||||
<pre id="test">
|
<pre id="test">
|
||||||
<script type="application/javascript">
|
<script type="application/javascript">
|
||||||
|
|
||||||
/** Test for Bug 1013412 **/
|
|
||||||
|
|
||||||
var rotation = 0;
|
var rotation = 0;
|
||||||
var rotationAdjusted = false;
|
var rotationAdjusted = false;
|
||||||
|
|
||||||
|
|
@ -80,7 +80,8 @@ document.getElementById("scrollbox").addEventListener("wheel", function (e) {
|
||||||
var iteration = 0;
|
var iteration = 0;
|
||||||
function runTest() {
|
function runTest() {
|
||||||
var content = document.getElementById('content');
|
var content = document.getElementById('content');
|
||||||
if (iteration < 300) { // enough iterations that we would scroll to the bottom of 'content'
|
// enough iterations that we would scroll to the bottom of 'content'
|
||||||
|
if (iteration < 600 && content.scrollTop != content.scrollTopMax) {
|
||||||
iteration++;
|
iteration++;
|
||||||
sendWheelAndPaint(content, 100, 10,
|
sendWheelAndPaint(content, 100, 10,
|
||||||
{ deltaMode: WheelEvent.DOM_DELTA_LINE,
|
{ deltaMode: WheelEvent.DOM_DELTA_LINE,
|
||||||
|
|
@ -89,8 +90,8 @@ function runTest() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var scrollbox = document.getElementById('scrollbox');
|
var scrollbox = document.getElementById('scrollbox');
|
||||||
is(content.scrollTop < content.scrollTopMax, true, "We should not have scrolled to the bottom of the scrollframe");
|
is(content.scrollTop, content.scrollTopMax, "We should have scrolled to the bottom of the scrollframe");
|
||||||
is(rotationAdjusted, true, "The rotation should have been adjusted");
|
is(rotationAdjusted, false, "The rotation should not have been adjusted");
|
||||||
SimpleTest.finish();
|
SimpleTest.finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -98,7 +99,11 @@ function startTest() {
|
||||||
// If we allow smooth scrolling the "smooth" scrolling may cause the page to
|
// 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
|
// glide past the scrollbox (which is supposed to stop the scrolling) and so
|
||||||
// we might end up at the bottom of the page.
|
// we might end up at the bottom of the page.
|
||||||
SpecialPowers.pushPrefEnv({"set": [["general.smoothScroll", false], ["test.events.async.enabled", true]]}, runTest);
|
SpecialPowers.pushPrefEnv({"set": [["general.smoothScroll", false],
|
||||||
|
["test.events.async.enabled", true],
|
||||||
|
["mousewheel.transaction.timeout", 100000],
|
||||||
|
["dom.event.wheel-event-groups.enabled", true]]},
|
||||||
|
runTest);
|
||||||
}
|
}
|
||||||
|
|
||||||
SimpleTest.waitForExplicitFinish();
|
SimpleTest.waitForExplicitFinish();
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,10 @@
|
||||||
<html>
|
<html>
|
||||||
<!--
|
<!--
|
||||||
https://bugzilla.mozilla.org/show_bug.cgi?id=1013412
|
https://bugzilla.mozilla.org/show_bug.cgi?id=1013412
|
||||||
|
https://bugzilla.mozilla.org/show_bug.cgi?id=1168182
|
||||||
-->
|
-->
|
||||||
<head>
|
<head>
|
||||||
<title>Test for Bug 1013412</title>
|
<title>Test for Bug 1013412 and 1168182</title>
|
||||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||||
<script src="/tests/SimpleTest/EventUtils.js"></script>
|
<script src="/tests/SimpleTest/EventUtils.js"></script>
|
||||||
<script src="/tests/SimpleTest/paint_listener.js"></script>
|
<script src="/tests/SimpleTest/paint_listener.js"></script>
|
||||||
|
|
@ -45,7 +46,8 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1013412
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1161206">Mozilla Bug 1161206</a>
|
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1013412">Mozilla Bug 1013412</a>
|
||||||
|
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1168182">Mozilla Bug 1168182</a>
|
||||||
<p id="display"></p>
|
<p id="display"></p>
|
||||||
<div id="content">
|
<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>
|
<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>
|
||||||
|
|
@ -79,12 +81,13 @@ document.getElementById("scrollbox").addEventListener("wheel", function(e) {
|
||||||
|
|
||||||
async function test() {
|
async function test() {
|
||||||
var content = document.getElementById("content");
|
var content = document.getElementById("content");
|
||||||
for (let i = 0; i < 300; i++) { // enough iterations that we would scroll to the bottom of 'content'
|
// enough iterations that we would scroll to the bottom of 'content'
|
||||||
|
for (let i = 0; i < 600 && content.scrollTop != content.scrollTopMax; i++) {
|
||||||
await promiseNativeWheelAndWaitForWheelEvent(content, 100, 150, 0, -5);
|
await promiseNativeWheelAndWaitForWheelEvent(content, 100, 150, 0, -5);
|
||||||
}
|
}
|
||||||
is(content.scrollTop > 0, true, "We should have scrolled down somewhat");
|
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(content.scrollTop, content.scrollTopMax, "We should have scrolled to the bottom of the scrollframe");
|
||||||
is(rotationAdjusted, true, "The rotation should have been adjusted");
|
is(rotationAdjusted, false, "The rotation should not have been adjusted");
|
||||||
}
|
}
|
||||||
|
|
||||||
SimpleTest.waitForExplicitFinish();
|
SimpleTest.waitForExplicitFinish();
|
||||||
|
|
@ -92,7 +95,9 @@ SimpleTest.waitForExplicitFinish();
|
||||||
// If we allow smooth scrolling the "smooth" scrolling may cause the page to
|
// 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
|
// glide past the scrollbox (which is supposed to stop the scrolling) and so
|
||||||
// we might end up at the bottom of the page.
|
// we might end up at the bottom of the page.
|
||||||
pushPrefs([["general.smoothScroll", false]])
|
pushPrefs([["general.smoothScroll", false],
|
||||||
|
["mousewheel.transaction.timeout", 100000],
|
||||||
|
["dom.event.wheel-event-groups", true]])
|
||||||
.then(waitUntilApzStable)
|
.then(waitUntilApzStable)
|
||||||
.then(test)
|
.then(test)
|
||||||
.then(SimpleTest.finish, SimpleTest.finishWithFailure);
|
.then(SimpleTest.finish, SimpleTest.finishWithFailure);
|
||||||
|
|
|
||||||
|
|
@ -48,12 +48,19 @@ async function scrollWheelOver(element, deltaY) {
|
||||||
async function test() {
|
async function test() {
|
||||||
var outer = document.getElementById("outer-frame");
|
var outer = document.getElementById("outer-frame");
|
||||||
var inner = document.getElementById("inner-frame");
|
var inner = document.getElementById("inner-frame");
|
||||||
var innerContent = document.getElementById("inner-content");
|
|
||||||
|
|
||||||
// Register a wheel event listener that records the target of
|
// Register a wheel event listener that records the target of
|
||||||
// the last wheel event, so that we can make assertions about it.
|
// the last wheel event, so that we can make assertions about it.
|
||||||
var lastWheelTarget;
|
let lastWheelTarget;
|
||||||
var wheelTargetRecorder = function(e) { lastWheelTarget = e.target; };
|
let firstWheelTarget;
|
||||||
|
let wheelEventOccurred = false;
|
||||||
|
var wheelTargetRecorder = function(e) {
|
||||||
|
if (!wheelEventOccurred) {
|
||||||
|
firstWheelTarget = e.target;
|
||||||
|
wheelEventOccurred = true;
|
||||||
|
}
|
||||||
|
lastWheelTarget = e.target;
|
||||||
|
};
|
||||||
window.addEventListener("wheel", wheelTargetRecorder);
|
window.addEventListener("wheel", wheelTargetRecorder);
|
||||||
|
|
||||||
// Scroll |outer| to the bottom.
|
// Scroll |outer| to the bottom.
|
||||||
|
|
@ -61,8 +68,8 @@ async function test() {
|
||||||
await scrollWheelOver(outer, -10);
|
await scrollWheelOver(outer, -10);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify that this has brought |inner| under the wheel.
|
is(lastWheelTarget, firstWheelTarget,
|
||||||
is(lastWheelTarget, innerContent, "'inner-content' should have been brought under the wheel");
|
"target " + lastWheelTarget.id + " should be " + lastWheelTarget.id);
|
||||||
window.removeEventListener("wheel", wheelTargetRecorder);
|
window.removeEventListener("wheel", wheelTargetRecorder);
|
||||||
|
|
||||||
// Immediately after, scroll it back up a bit.
|
// Immediately after, scroll it back up a bit.
|
||||||
|
|
@ -129,7 +136,9 @@ SimpleTest.waitForExplicitFinish();
|
||||||
// inputs since this test is specifically testing things related to wheel
|
// inputs since this test is specifically testing things related to wheel
|
||||||
// transactions.
|
// transactions.
|
||||||
pushPrefs([["general.smoothScroll", false],
|
pushPrefs([["general.smoothScroll", false],
|
||||||
["apz.test.mac.synth_wheel_input", true]])
|
["apz.test.mac.synth_wheel_input", true],
|
||||||
|
["mousewheel.transaction.timeout", 1500],
|
||||||
|
["dom.event.wheel-event-groups", true]])
|
||||||
.then(waitUntilApzStable)
|
.then(waitUntilApzStable)
|
||||||
.then(test)
|
.then(test)
|
||||||
.then(SimpleTest.finish, SimpleTest.finishWithFailure);
|
.then(SimpleTest.finish, SimpleTest.finishWithFailure);
|
||||||
|
|
|
||||||
|
|
@ -7099,6 +7099,11 @@ nsresult PresShell::EventHandler::HandleEventUsingCoordinates(
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wheel events only apply to elements. If this is a wheel event, attempt to
|
||||||
|
// update the event target from the current wheel transaction before we
|
||||||
|
// compute the element from the target frame.
|
||||||
|
eventTargetData.UpdateWheelEventTarget(aGUIEvent);
|
||||||
|
|
||||||
if (!eventTargetData.ComputeElementFromFrame(aGUIEvent)) {
|
if (!eventTargetData.ComputeElementFromFrame(aGUIEvent)) {
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
@ -11813,6 +11818,34 @@ bool PresShell::EventHandler::EventTargetData::ComputeElementFromFrame(
|
||||||
return !!mContent;
|
return !!mContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PresShell::EventHandler::EventTargetData::UpdateWheelEventTarget(
|
||||||
|
WidgetGUIEvent* aGUIEvent) {
|
||||||
|
MOZ_ASSERT(aGUIEvent);
|
||||||
|
|
||||||
|
if (aGUIEvent->mMessage != eWheel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If dom.event.wheel-event-groups.enabled is not set or the stored
|
||||||
|
// event target is removed, we will not get a event target frame from the
|
||||||
|
// wheel transaction here.
|
||||||
|
nsIFrame* groupFrame = WheelTransaction::GetEventTargetFrame();
|
||||||
|
if (!groupFrame) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the browsing context is no longer the same as the context of the
|
||||||
|
// current wheel transaction, do not override the event target.
|
||||||
|
if (!groupFrame->PresContext() || !groupFrame->PresShell() ||
|
||||||
|
groupFrame->PresContext() != GetPresContext()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If dom.event.wheel-event-groups.enabled is set and whe have a stored
|
||||||
|
// event target from the wheel transaction, override the event target.
|
||||||
|
SetFrameAndComputePresShellAndContent(groupFrame, aGUIEvent);
|
||||||
|
}
|
||||||
|
|
||||||
void PresShell::EventHandler::EventTargetData::UpdateTouchEventTarget(
|
void PresShell::EventHandler::EventTargetData::UpdateTouchEventTarget(
|
||||||
WidgetGUIEvent* aGUIEvent) {
|
WidgetGUIEvent* aGUIEvent) {
|
||||||
MOZ_ASSERT(aGUIEvent);
|
MOZ_ASSERT(aGUIEvent);
|
||||||
|
|
|
||||||
|
|
@ -2209,6 +2209,16 @@ class PresShell final : public nsStubDocumentObserver,
|
||||||
*/
|
*/
|
||||||
void UpdateTouchEventTarget(WidgetGUIEvent* aGUIEvent);
|
void UpdateTouchEventTarget(WidgetGUIEvent* aGUIEvent);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UpdateWheelEventTarget() updates mFrame, mPresShell, and mContent if
|
||||||
|
* aGUIEvent is a wheel event and aGUIEvent should be grouped with prior
|
||||||
|
* wheel events.
|
||||||
|
*
|
||||||
|
* @param aGUIEvent The handled event. If it's not a wheel event,
|
||||||
|
* this method does nothing.
|
||||||
|
*/
|
||||||
|
void UpdateWheelEventTarget(WidgetGUIEvent* aGUIEvent);
|
||||||
|
|
||||||
RefPtr<PresShell> mPresShell;
|
RefPtr<PresShell> mPresShell;
|
||||||
nsIFrame* mFrame = nullptr;
|
nsIFrame* mFrame = nullptr;
|
||||||
nsCOMPtr<nsIContent> mContent;
|
nsCOMPtr<nsIContent> mContent;
|
||||||
|
|
|
||||||
|
|
@ -2513,6 +2513,13 @@
|
||||||
value: @IS_NOT_NIGHTLY_BUILD@
|
value: @IS_NOT_NIGHTLY_BUILD@
|
||||||
mirror: always
|
mirror: always
|
||||||
|
|
||||||
|
# Whether wheel event target's should be grouped. When enabled, all wheel
|
||||||
|
# events that occur in a given wheel transaction have the same event target.
|
||||||
|
- name: dom.event.wheel-event-groups.enabled
|
||||||
|
type: bool
|
||||||
|
value: @IS_NIGHTLY_BUILD@
|
||||||
|
mirror: always
|
||||||
|
|
||||||
# Whether WheelEvent should return pixels instead of lines for
|
# Whether WheelEvent should return pixels instead of lines for
|
||||||
# WheelEvent.deltaX/Y/Z, when deltaMode hasn't been checked.
|
# WheelEvent.deltaX/Y/Z, when deltaMode hasn't been checked.
|
||||||
#
|
#
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,2 @@
|
||||||
prefs: [apz.scrollend-event.content.enabled:true]
|
prefs: [apz.scrollend-event.content.enabled:true, dom.event.wheel-event-groups.enabled:true, mousewheel.transaction.timeout:500]
|
||||||
lsan-allowed: [Alloc, MakeUnique, Malloc, Realloc, XPCNativeInterface::NewInstance, XPCNativeSet::NewInstance, XPCNativeSet::NewInstanceMutate, XPCWrappedNative::GetNewOrUsed, XPCWrappedNativeProto::GetNewOrUsed, mozilla::dom::WebExtensionInit::Init, mozilla::extensions::MatchPatternCore::MatchPatternCore, mozilla::extensions::MatchPatternSet::Constructor, mozilla::extensions::MatchPatternSet::GetPatterns, mozilla::extensions::ParseGlobs, mozilla::extensions::PermittedSchemes, mozilla::extensions::WebExtensionPolicy::Constructor, mozilla::extensions::WebExtensionPolicy::WebExtensionPolicy, mozilla::extensions::WebExtensionPolicyCore::WebExtensionPolicyCore, mozilla::net::nsStandardURL::TemplatedMutator, nsDynamicAtom::Create, nsJARURI::Mutator::SetSpecBaseCharset]
|
lsan-allowed: [Alloc, MakeUnique, Malloc, Realloc, XPCNativeInterface::NewInstance, XPCNativeSet::NewInstance, XPCNativeSet::NewInstanceMutate, XPCWrappedNative::GetNewOrUsed, XPCWrappedNativeProto::GetNewOrUsed, mozilla::dom::WebExtensionInit::Init, mozilla::extensions::MatchPatternCore::MatchPatternCore, mozilla::extensions::MatchPatternSet::Constructor, mozilla::extensions::MatchPatternSet::GetPatterns, mozilla::extensions::ParseGlobs, mozilla::extensions::PermittedSchemes, mozilla::extensions::WebExtensionPolicy::Constructor, mozilla::extensions::WebExtensionPolicy::WebExtensionPolicy, mozilla::extensions::WebExtensionPolicyCore::WebExtensionPolicyCore, mozilla::net::nsStandardURL::TemplatedMutator, nsDynamicAtom::Create, nsJARURI::Mutator::SetSpecBaseCharset]
|
||||||
|
|
|
||||||
|
|
@ -277,7 +277,13 @@ async function prepareRunningTests()
|
||||||
function* testBody()
|
function* testBody()
|
||||||
{
|
{
|
||||||
yield* testRichListbox("richlistbox");
|
yield* testRichListbox("richlistbox");
|
||||||
|
|
||||||
|
// Perform a mousedown to ensure the wheel transaction from the previous test
|
||||||
|
// does not impact the next test.
|
||||||
|
synthesizeMouse(document.scrollingElement, 0, 0, {type: "mousedown"}, window);
|
||||||
yield* testArrowScrollbox("hscrollbox");
|
yield* testArrowScrollbox("hscrollbox");
|
||||||
|
|
||||||
|
synthesizeMouse(document.scrollingElement, -1, -1, {type: "mousedown"}, window);
|
||||||
yield* testArrowScrollbox("vscrollbox");
|
yield* testArrowScrollbox("vscrollbox");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue