Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mouseleave event isn't fired when moving the mouse outside the window (Windows) #611

Closed
MartinMa opened this issue Aug 27, 2014 · 33 comments

Comments

@MartinMa
Copy link
Contributor

I have a simple div with a css hover effect changing the background color. When I move the mouse outside of the window area, the div keeps its hover style. It should actually go back to its normal style. Presumable the mouseleave event was "swallowed".

For a demonstration video see here: https://www.screenr.com/4M0N

It happens using the frameless view and using the windows frame view. Even though the latter is more difficult to reproduce (move the mouse faster).

You can reproduce it with this sample code:

<!DOCTYPE html>
<html>
  <head>
    <title>mouseleave</title>

        <style>
            .box {
                position: absolute;
                top: 0;
                right: 0;
                bottom: 0;
                left: 10%;
            }
            .box:hover {
                background-color: red;
            }
        </style>
  </head>
  <body>
        <div class="box"></div>
  </body>
</html>

My setup:
Windows 8.1 7
atom-shell 0.15.7

@frankhale
Copy link
Contributor

I just tested on Windows 8.1 and Atom-Shell 0.15.7 and your example seems to work fine for me on both a framed and unframed browser window. I cannot reproduce the behavior I see on your video.

@MartinMa
Copy link
Contributor Author

The video above was made on Windows 7. Sorry for the confusion.

But I just tested it again on another computer (this time Windows 8.1 and atomshell 0.15.9), same behavior.
@frankhale Did you try the frameless mode? frame: falsewhen createing the BrowserWindow? When you keep the frame it's hard to reproduce. You have to manically move the mouse back and forth ;)

@frankhale
Copy link
Contributor

Yes, I tested both framed and non-framed windows. I'll test on another
machine when I get home as well.

On Wed, Aug 27, 2014 at 4:53 PM, Martin Mädler notifications@github.com
wrote:

The video above was made on Windows 7. Sorry for the confusion.

But I just tested it again on another computer (this time Windows 8.1 and
atomshell 0.15.9), same behavior.
@frankhale https://github.com/frankhale Did you try the frameless mode? frame:
falsewhen createing the BrowserWindow? When you keep the frame it's hrad
to reproduce. You have to manically move the mouse back and forth ;)


Reply to this email directly or view it on GitHub
#611 (comment).

@frankhale
Copy link
Contributor

I have access to a few machines and one is working fine but the other exhibits the behavior you are talking about. Both Windows 8.1. That is crazy! I'd have expected it would be consistent.

@zcbenz
Copy link
Member

zcbenz commented Dec 15, 2014

There is dragging border in frameless window, and it is causing the window to loose mouse events.

Possibly related: #741.

@frankhale
Copy link
Contributor

Okay, I have created an app window that is frameless with a custom titlebar and drag region. I am definitely seeing the loss of mouse events. I have custom hover effects on the various minimize, maximize, close buttons and if the mouse pointer leaves the main window immediately after it triggered a hover on one of these buttons the hover effect is not removed to return the button to it's original state.

@MartinMa
Copy link
Contributor Author

On Windows, I think it's necessary to track the mouse events by calling the TrackMouseEvent function in order to be able to receive the WM_MOUSELEAVE message.

I don't fully comprehend how and where atom-shell handles windows messages. Where do I actually find the message loop?

Anyhow, this is basically how chromium (Google Chrome) seems to do it in their HWNDMessageHandler class.

if (message == WM_MOUSEMOVE || message == WM_NCMOUSEMOVE)
{
    // Windows only fires WM_MOUSELEAVE events if the application begins
    // "tracking" mouse events for a given HWND during WM_MOUSEMOVE events.
    // We need to call |TrackMouseEvents| to listen for WM_MOUSELEAVE.
    TrackMouseEvents((message == WM_NCMOUSEMOVE) ? TME_NONCLIENT | TME_LEAVE : TME_LEAVE);
}
else if (message == WM_MOUSELEAVE || message == WM_NCMOUSELEAVE)
{
    // Reset our tracking flags so future mouse movement over this
    // window results in a new tracking session.
    active_mouse_tracking_flags_ = 0;
}
void HWNDMessageHandler::TrackMouseEvents(DWORD mouse_tracking_flags) {
    // Begin tracking mouse events for this HWND so that we get WM_MOUSELEAVE
    // when the user moves the mouse outside this HWND's bounds.
    if (active_mouse_tracking_flags_ == 0 || mouse_tracking_flags & TME_CANCEL)
    {
        if (mouse_tracking_flags & TME_CANCEL)
        {
            // We're about to cancel active mouse tracking, so empty out the stored
            // state.
            active_mouse_tracking_flags_ = 0;
        }
        else
        {
            active_mouse_tracking_flags_ = mouse_tracking_flags;
        }

        TRACKMOUSEEVENT tme;
        tme.cbSize = sizeof(tme);
        tme.dwFlags = mouse_tracking_flags;
        tme.hwndTrack = hwnd();
        tme.dwHoverTime = 0;
        TrackMouseEvent(&tme);
    }
    else if (mouse_tracking_flags != active_mouse_tracking_flags_)
    {
        TrackMouseEvents(active_mouse_tracking_flags_ | TME_CANCEL);
        TrackMouseEvents(mouse_tracking_flags);
    }
}

DWORD active_mouse_tracking_flags_ is a member variable initialized with 0. The code get's slightly more complicated, when incorporating SetCapture and ReleaseCapture for proper right-click handling on the caption bar ("to ensure we only show the sys-menu when the button down and up are both on the caption").

@Keavon
Copy link

Keavon commented Apr 20, 2015

This also happens when moving your mouse from an item with a hover style to an item with the -webkit-app-region: drag; style applied in addition to moving from an item with a hover style applied to outside the frameless window.

@martpie
Copy link

martpie commented Jul 2, 2015

I can confirm what @Keavon is saying.

It also happens when moving the mouse from an :hover-ed element to a -webkit-app-region: drag;, the first element still get the :hover

edit: seems it will be a wontfix #1354

@abnerlee
Copy link

abnerlee commented Aug 2, 2015

Does anyway found solutions for this bug?

@martpie
Copy link

martpie commented Aug 3, 2015

I'm afraid this is related to Chromium, so we can't really make something

@sapjax
Copy link

sapjax commented Aug 20, 2015

@TylerM89
Copy link

This seems to happen in both frameless and non-frameless windows, with and without 'webkit-app-region'. It doesn't seem to happen in Atom IDE or Chrome. It does happen in Visual Studio code though and my test apps.

Anyone have insight into it?

@hadarge
Copy link

hadarge commented Feb 11, 2016

Reproduce in my app too :(
Anyone have insight into it?

@TylerM89
Copy link

Tried a bunch different things to get it working, but it happens in every version of Chromium, including frameless Chrome apps. We submitted an issue to Chromium but they upgraded to VS2015 that same day, so I imagine they got flooded with more important bugs.

@kieferlam
Copy link

Does anyone have a work around for this problem? I'm getting the same issue.

@louisameline
Copy link

I wrote a workaround for this, not ideal but it's enough for my use case. See how the window mouseleave is detected, even when leaving the app-region top bar:

fix

The code is here: https://gist.github.com/louisameline/1213bb112c6cb12a98b2ab525dfb8b07

@kieferlam
Copy link

Nice one! Perhaps you could prevent the events triggering while a window is overlaying it by checking for focus?

@louisameline
Copy link

I thought about that but then a blurred window wouldn't react to mouse hovering, even if there were no windows on top of it. That's not the usual behavior and I'm not sure it would be better.

@louisameline
Copy link

When I say the window wouldn't react, I'm actually thinking about my app-region titlebar, which wouldn't reappear because it can't catch mouse moves and needs coordinates calculations instead. But you're probably right, with appropriate modifications on this bar it may work as expected.

@louisameline
Copy link

I have enabled the check for the focused window by default, but you can disable it by setting regionAppOptimized = true. Most of the time, app regions will fail to respond if you don't set to true:

fix

I thought it would be smart to apply a -webkit-app-region: drag property on the titlebar after it got its opacity back (to let it catch mouseenter events), but that trick doesn't work. The view must have that info in cache and does not obey the CSS change quickly enough.

@codebytere
Copy link
Member

Given that there exist workarounds, this is something that isn't on our current roadmap and which I am thus goin to tag as wontfix.

@lanistor
Copy link

Appeal to reopen this issue!

@frankhale
Copy link
Contributor

I don't think they will fix it, even their Github Desktop app exhibits this unfortunate behavior. Minimize the app and then hover over the taskbar icon in Windows and you'll notice that they cannot react to the minimize state and the minimize icon is still highlighted in the preview.

Here is a screenshot showing (yeah it's hard to see but it's there)

github-desktop-electron-mouse-event-outside-window-issue

@lanistor
Copy link

lanistor commented Dec 21, 2019

I just found a hacky solution, which works for me, use mainWindow.setResizable(true).
I had tested on Window10, with electron v6.0.2.

@SlicerNorthD
Copy link

mouseleave event doesn't fire with the below window properties set but if resizable is set to true then it works. Using windows 10 with electron 7.3.1. Any way to work around this?

alwaysOnTop: true,
skipTaskbar: true,
thickFrame: false,
frame: false,
resizable: false,
webPreferences: {
nodeIntegration: true,
}

@wangdingwei
Copy link

mouseleave event doesn't fire with the below window properties set but if resizable is set to true then it works. Using windows 10 with electron 7.3.1. Any way to work around this?

alwaysOnTop: true,
skipTaskbar: true,
thickFrame: false,
frame: false,
resizable: false,
webPreferences: {
nodeIntegration: true,
}

same problem

@sssooonnnggg
Copy link
Contributor

I add a fix for this bug, see #29721

@sssooonnnggg
Copy link
Contributor

我英文不太好,尝试解释一下 bug 产生的细节:

经研究发现:

shell/browser/ui/views/frameless_view.cc 里的处理有问题:

当 resizable 设置为 false 时,鼠标经过窗口边缘时(窗口边缘设置为5个像素[kResizeInsideBoundsSize]), 调用 FramelessView::ResizingBorderHitTest 时返回了 HTBORDER。

这会导致 Windows 误以为鼠标离开了窗口区域,从而向窗口发送了一个 WM_MOUSELEAVE 事件,实际上鼠标依然在窗口内。

chromium 在 legacy_render_widget_host_win.cc 里处理 WM_MOUSELEAVE 时,会通过 ::GetCursorPos()::WindowFromPoint() 来获取鼠标当前所在窗口 :

LRESULT LegacyRenderWidgetHostHWND::OnMouseLeave(UINT message,
                                                 WPARAM w_param,
                                                 LPARAM l_param) {
  mouse_tracking_enabled_ = false;
  LRESULT ret = 0;
  HWND capture_window = ::GetCapture();
  if ((capture_window != GetParent()) && GetWindowEventTarget(GetParent())) {
    // We should send a WM_MOUSELEAVE to the parent window only if the mouse
    // has moved outside the bounds of the parent.
    POINT cursor_pos;
    ::GetCursorPos(&cursor_pos);

    // WindowFromPoint returns the top-most HWND. As hwnd() may not
    // respond with HTTRANSPARENT to a WM_NCHITTEST message,
    // it may be returned.
    HWND window_from_point = ::WindowFromPoint(cursor_pos);
    if (window_from_point != GetParent() &&
        (capture_window || window_from_point != hwnd())) {
      bool msg_handled = false;
      ret = GetWindowEventTarget(GetParent())->HandleMouseMessage(
          message, w_param, l_param, &msg_handled);
      SetMsgHandled(msg_handled);
    }
  }
  return ret;
}

此时因为 WM_NCHITTEST 返回了 HTBORDER,因此虽然触发了 WM_MOUSELEAVE,但是鼠标仍然在窗口内部。

所以会导致 window_from_point != hwnd() 这个条件不会满足,进而 GetParent())->HandleMouseMessage(message, w_param, l_param, &msg_handled) 这个函数不会被执行,从而导致 DOM 的 hover 状态不会被清除。

所以当 BrowserWindow 的 resizable 属性设置为 false 时,NCHitTest 应该返回 HTCLIENT,这样才能让 Windows 在鼠标真正离开窗口时触发 WM_MOUSELEAVE,解决 hover 状态清除不掉的问题。

zcbenz pushed a commit that referenced this issue Jun 21, 2021
#611) (#29799)

Co-authored-by: sssooonnnggg <sssooonnnggg111@gmail.com>
zcbenz pushed a commit that referenced this issue Jun 21, 2021
#611) (#29800)

Co-authored-by: sssooonnnggg <sssooonnnggg111@gmail.com>
zcbenz pushed a commit that referenced this issue Jun 21, 2021
#611) (#29801)

Co-authored-by: sssooonnnggg <sssooonnnggg111@gmail.com>
gerhardberger pushed a commit to gerhardberger/electron that referenced this issue Dec 26, 2021
…resizable (electron#611) (electron#29721)"

This reverts commit e54667e.

Around allows interactions within the non-client region so that the
floating window allows dragging. This also affects modals which
are not resizable.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests