Skip to content

Commit 672ba08

Browse files
Bug 1979332 - part 2: Filter out WM_MOUSEMOVE messages which are for temporarily restoring the mouse cursor r=dom-core,win-reviewers,edgar,gstoll
It seems that Wacom tablet driver sometimes synthesizes `WM_MOUSEMOVE` at last mouse cursor location set by a real mouse while the pen tablet overrides the mouse cursor position with its pen's position and synthesizes another pen move for overriding the mouse cursor again. Moreover, they do that continuously. Currently, we ignore mouse move events from any input sources ("mouse" and "pen") if the position is exactly same as the position of the last dispatched `eMouseMove`. However, for avoiding the odd same position mouse moves caused by them, we should store the last pointer position of mouse and non-mouse pointer separately. Then, we can compare the new mouse move position and the last pointer position of the input source and pointerId. NOTE: I tried to write a test using `nsIWidget::SynthesizeNativeMouseEvent` and `nsIWidget::SynthesizeNativePenInput`. However, I couldn't synthesize a `mousemove` whose `inputSource` is "pen" and anyway, `SynthesizeNativeMouseEvent` does not support to check the situation [1]. 1. https://searchfox.org/mozilla-central/rev/820596a140570007ce22a6c137ce2520676cfffe/widget/windows/nsWindow.cpp#6186-6189 Differential Revision: https://phabricator.services.mozilla.com/D259754
1 parent 05022d5 commit 672ba08

File tree

2 files changed

+179
-29
lines changed

2 files changed

+179
-29
lines changed

widget/windows/nsWindow.cpp

Lines changed: 176 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -331,10 +331,6 @@ UINT nsWindow::sRollupMsgId = 0;
331331
HWND nsWindow::sRollupMsgWnd = nullptr;
332332
UINT nsWindow::sHookTimerId = 0;
333333

334-
// Used to prevent dispatching mouse events that do not originate from user
335-
// input.
336-
POINT nsWindow::sLastMouseMovePoint = {0};
337-
338334
bool nsWindow::sIsRestoringSession = false;
339335

340336
bool nsWindow::sTouchInjectInitialized = false;
@@ -443,6 +439,161 @@ static bool gInitializedVirtualDesktopManager = false;
443439
// coordinate this many milliseconds:
444440
#define HITTEST_CACHE_LIFETIME_MS 50
445441

442+
/**
443+
* Used to prevent dispatching eMouseMove events that do not originate from user
444+
* input.
445+
*/
446+
class nsWindow::LastMouseMoveData {
447+
public:
448+
/**
449+
* Return true if eMouseMove whose point is aPoint, input source is
450+
* aInputSource and pointerId is aPointerId should not be dispatched to avoid
451+
* unexpected behavior in content.
452+
*
453+
* @param aPoint The ref-point where the new mouse move occurred.
454+
* @param aInputSource The input source of the mouse move event.
455+
* @param aPointerId The pointerId of the mouse move event. If there is
456+
* no specific one because of a mouse input, specify 0
457+
* which won't be referred anyway.
458+
* @return true if the event should not cause eMouseMove event in the content.
459+
*/
460+
template <typename POINTOrLayoutDeviceIntPoint>
461+
[[nodiscard]] static bool ShouldIgnoreMouseMoveOf(
462+
const POINTOrLayoutDeviceIntPoint& aPoint, uint16_t aInputSource,
463+
uint32_t aPointerId) {
464+
return sInstance.ShouldIgnoreMouseMoveOfImpl(aPoint, aInputSource,
465+
aPointerId);
466+
}
467+
468+
/**
469+
* Forget the last mouse move point caused by both mouse and non-mouse.
470+
*/
471+
static void Clear() { sInstance.ClearImpl(); }
472+
473+
/**
474+
* Called when nsWindow will dispatch an eMouseMove event.
475+
*
476+
* @param aPoint The ref-point where the native mouse move occurred.
477+
* @param aInputSource The input source of the mouse move event.
478+
* @param aPointerId The pointerId of the mouse move event. If there is
479+
* no specific one because of a mouse input, specify 0
480+
* which won't be referred anyway.
481+
*/
482+
static void WillDispatchMouseMoveOf(const LayoutDeviceIntPoint& aPoint,
483+
uint16_t aInputSource,
484+
uint32_t aPointerId) {
485+
sInstance.WillDispatchMouseMoveOfImpl(aPoint, aInputSource, aPointerId);
486+
}
487+
488+
private:
489+
LastMouseMoveData() = default;
490+
491+
template <typename POINTOrLayoutDeviceIntPoint>
492+
[[nodiscard]] bool ShouldIgnoreMouseMoveOfImpl(
493+
const POINTOrLayoutDeviceIntPoint& aPoint, uint16_t aInputSource,
494+
uint32_t aPointerId) const {
495+
// Suppress mouse moves caused by widget creation which is fired at same
496+
// screen position with the last mouse position. And also we should ignore
497+
// odd mouse move events which are caused by some tablet drivers. They try
498+
// to restore the mouse position and restore the pen position again
499+
// continuously. We don't want such events for avoiding the mouse cursor to
500+
// flicker, avoiding to dispatching synthesized mouse/pointer boundary
501+
// events and avoiding to switch `:hover` state because they waste a lot of
502+
// CPU resource. So, let's ignore native mouse move events which occurs at
503+
// the same position of the last point with the same pointer.
504+
return PointsEqual(LastPoint(aInputSource, aPointerId), aPoint);
505+
}
506+
507+
void ClearImpl() {
508+
mLastPointByMouse.reset();
509+
mLastPointAndPointerIdByNonMouse.reset();
510+
}
511+
512+
void WillDispatchMouseMoveOfImpl(const LayoutDeviceIntPoint& aPoint,
513+
uint16_t aInputSource, uint32_t aPointerId) {
514+
POINT& lastPoint = [&]() -> POINT& {
515+
if (IsMouse(aInputSource)) {
516+
if (mLastPointByMouse.isNothing()) {
517+
mLastPointByMouse.emplace();
518+
}
519+
return mLastPointByMouse.ref();
520+
}
521+
if (mLastPointAndPointerIdByNonMouse.isNothing()) {
522+
mLastPointAndPointerIdByNonMouse.emplace();
523+
}
524+
mLastPointAndPointerIdByNonMouse->mPointerId = aPointerId;
525+
return mLastPointAndPointerIdByNonMouse->mPoint;
526+
}();
527+
lastPoint.x = aPoint.x.value;
528+
lastPoint.y = aPoint.y.value;
529+
}
530+
531+
/**
532+
* Return true if aInputSource is "mouse".
533+
*/
534+
[[nodiscard]] static bool IsMouse(uint16_t aInputSource) {
535+
return aInputSource == MouseEvent_Binding::MOZ_SOURCE_MOUSE;
536+
}
537+
538+
/**
539+
* Return true if aPoint and aOtherPoint are the same point.
540+
*/
541+
template <typename PointType>
542+
[[nodiscard]] static bool PointsEqual(const Maybe<POINT>& aPoint,
543+
const PointType& aOtherPoint);
544+
545+
/**
546+
* Return the last mouse move event position of aPointerId if aInputSource is
547+
* not "mouse" or of the mouse if aInputSource is "mouse".
548+
*/
549+
[[nodiscard]] Maybe<POINT> LastPoint(uint16_t aInputSource,
550+
uint32_t aPointerId) const {
551+
return IsMouse(aInputSource)
552+
? mLastPointByMouse
553+
: (IsLastNonMousePointerId(aPointerId)
554+
? Some(mLastPointAndPointerIdByNonMouse->mPoint)
555+
: Nothing());
556+
}
557+
558+
/**
559+
* Return true if aPointerId is same as the last non-mouse pointerId.
560+
*/
561+
[[nodiscard]] bool IsLastNonMousePointerId(uint32_t aPointerId) const {
562+
return mLastPointAndPointerIdByNonMouse &&
563+
mLastPointAndPointerIdByNonMouse->mPointerId == aPointerId;
564+
}
565+
566+
// We don't need to take care of pointerId of mouse because it's ignored by
567+
// PresShell, PointerEventHandler and EventStateManager to handle mouse
568+
// boundary events and CSS hover state.
569+
Maybe<POINT> mLastPointByMouse;
570+
// We need to manage the last non-mouse pointer location and pointerId as a
571+
// pair because pointerId is meaningful for the non-mouse input sources.
572+
struct LastNonMousePointerMoveData {
573+
POINT mPoint = {0};
574+
uint32_t mPointerId = 0;
575+
};
576+
Maybe<LastNonMousePointerMoveData> mLastPointAndPointerIdByNonMouse;
577+
578+
static LastMouseMoveData sInstance;
579+
};
580+
581+
template <>
582+
bool nsWindow::LastMouseMoveData::PointsEqual(const Maybe<POINT>& aPoint,
583+
const POINT& aOtherPoint) {
584+
return aPoint.isSome() && aPoint->x == aOtherPoint.x &&
585+
aPoint->y == aOtherPoint.y;
586+
}
587+
588+
template <>
589+
bool nsWindow::LastMouseMoveData::PointsEqual(
590+
const Maybe<POINT>& aPoint, const LayoutDeviceIntPoint& aOtherPoint) {
591+
return aPoint.isSome() && aPoint->x == aOtherPoint.x.value &&
592+
aPoint->y == aOtherPoint.y.value;
593+
}
594+
595+
nsWindow::LastMouseMoveData nsWindow::LastMouseMoveData::sInstance;
596+
446597
#if defined(ACCESSIBILITY)
447598

448599
namespace mozilla {
@@ -4052,15 +4203,16 @@ bool nsWindow::DispatchMouseEvent(EventMessage aEventMessage, WPARAM wParam,
40524203
LayoutDeviceIntPoint mpScreen = eventPoint + WidgetToScreenOffset();
40534204

40544205
// Suppress mouse moves caused by widget creation. Make sure to do this early
4055-
// so that we update sLastMouseMovePoint even for touch-induced mousemove
4056-
// events.
4206+
// so that we update sLastMouseMovePointByAnyPointer even for touch-induced
4207+
// mousemove events.
40574208
if (aEventMessage == eMouseMove) {
4058-
if ((sLastMouseMovePoint.x == mpScreen.x.value) &&
4059-
(sLastMouseMovePoint.y == mpScreen.y.value)) {
4209+
if (LastMouseMoveData::ShouldIgnoreMouseMoveOf(
4210+
mpScreen, aInputSource,
4211+
aPointerInfo ? aPointerInfo->pointerId : 0)) {
40604212
return result;
40614213
}
4062-
sLastMouseMovePoint.x = mpScreen.x;
4063-
sLastMouseMovePoint.y = mpScreen.y;
4214+
LastMouseMoveData::WillDispatchMouseMoveOf(
4215+
mpScreen, aInputSource, aPointerInfo ? aPointerInfo->pointerId : 0);
40644216
}
40654217

40664218
if (!bool(aIsNonclient) && WinUtils::GetIsMouseFromTouch(aEventMessage)) {
@@ -5055,16 +5207,14 @@ bool nsWindow::ProcessMessageInternal(UINT msg, WPARAM& wParam, LPARAM& lParam,
50555207
POINT mp;
50565208
mp.x = GET_X_LPARAM(lParamScreen);
50575209
mp.y = GET_Y_LPARAM(lParamScreen);
5058-
bool userMovedMouse = false;
5059-
if ((sLastMouseMovePoint.x != mp.x) || (sLastMouseMovePoint.y != mp.y)) {
5060-
userMovedMouse = true;
5061-
}
5062-
5063-
if (userMovedMouse) {
5064-
result = DispatchMouseEvent(
5065-
eMouseMove, wParam, lParam, false, MouseButton::ePrimary,
5066-
MOUSE_INPUT_SOURCE(),
5067-
mPointerEvents.GetCachedPointerInfo(msg, wParam));
5210+
const uint16_t inputSource = MOUSE_INPUT_SOURCE();
5211+
WinPointerInfo* const pointerInfo =
5212+
mPointerEvents.GetCachedPointerInfo(msg, wParam);
5213+
if (!LastMouseMoveData::ShouldIgnoreMouseMoveOf(
5214+
mp, inputSource, pointerInfo ? pointerInfo->pointerId : 0)) {
5215+
result =
5216+
DispatchMouseEvent(eMouseMove, wParam, lParam, false,
5217+
MouseButton::ePrimary, inputSource, pointerInfo);
50685218
DispatchPendingEvents();
50695219
}
50705220
} break;
@@ -5142,11 +5292,11 @@ bool nsWindow::ProcessMessageInternal(UINT msg, WPARAM& wParam, LPARAM& lParam,
51425292
mMousePresent = false;
51435293

51445294
// Check if the mouse is over the fullscreen transition window, if so
5145-
// clear sLastMouseMovePoint. This way the WM_MOUSEMOVE we get after the
5295+
// clear LastMouseMoveData. This way the WM_MOUSEMOVE we get after the
51465296
// transition window disappears will not be ignored, even if the mouse
51475297
// hasn't moved.
51485298
if (mTransitionWnd && WindowAtMouse() == mTransitionWnd) {
5149-
sLastMouseMovePoint = {0};
5299+
LastMouseMoveData::Clear();
51505300
}
51515301

51525302
// We need to check mouse button states and put them in for
@@ -6167,10 +6317,10 @@ nsresult nsWindow::SynthesizeNativeMouseEvent(
61676317
switch (aNativeMessage) {
61686318
case NativeMouseMessage::Move:
61696319
input.mi.dwFlags = MOUSEEVENTF_MOVE;
6170-
// Reset sLastMouseMovePoint so that even if we're moving the mouse
6171-
// to the position it's already at, we still dispatch a mousemove
6172-
// event, because the callers of this function expect that.
6173-
sLastMouseMovePoint = {0};
6320+
// Reset LastMouseMoveData so that even if we're moving the mouse to the
6321+
// position it's already at, we still dispatch a mousemove event, because
6322+
// the callers of this function expect that.
6323+
LastMouseMoveData::Clear();
61746324
break;
61756325
case NativeMouseMessage::ButtonDown:
61766326
case NativeMouseMessage::ButtonUp: {

widget/windows/nsWindow.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -727,9 +727,9 @@ class nsWindow final : public nsBaseWidget {
727727
static HWND sRollupMsgWnd;
728728
static UINT sHookTimerId;
729729

730-
// Used to prevent dispatching mouse events that do not originate from user
731-
// input.
732-
static POINT sLastMouseMovePoint;
730+
// Handle the last mouse point and the last non-mouse pointer point to stop
731+
// dispatching redundant eMouseMove events.
732+
class LastMouseMoveData;
733733

734734
nsClassHashtable<nsUint32HashKey, PointerInfo> mActivePointers;
735735

0 commit comments

Comments
 (0)