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

QtWebEngine recieves rogue tab keypresses #4579

Closed
prosoitos opened this issue Feb 15, 2019 · 62 comments
Closed

QtWebEngine recieves rogue tab keypresses #4579

prosoitos opened this issue Feb 15, 2019 · 62 comments
Labels
bug: behavior Something doesn't work as intended, but doesn't crash. component: keyinput Issues related to processing keypresses. component: QtWebEngine Issues related to the QtWebEngine backend, based on Chromium. priority: 0 - high Issues which are currently the primary focus.

Comments

@prosoitos
Copy link
Contributor

Version info:

qutebrowser v1.5.2
Git commit:
Backend: QtWebEngine (Chromium 69.0.3497.128)

CPython: 3.7.2
Qt: 5.12.1
PyQt: 5.12

sip: 4.19.14
colorama: no
pypeg2: 2.15
jinja2: 2.10
pygments: 2.3.1
yaml: 3.13
cssutils: no
attr: 18.2.0
PyQt5.QtWebEngineWidgets: yes
PyQt5.QtWebKitWidgets: yes
pdf.js: 2.0.943 (/usr/share/pdf.js/build/pdf.js)
sqlite: 3.27.1
QtNetwork SSL: OpenSSL 1.1.1a 20 Nov 2018

Style: QFusionStyle
Platform: Linux-4.20.8-arch1-1-ARCH-x86_64-with-arch, 64bit
Linux distribution: Arch Linux (arch)
Frozen: False
Imported from /usr/lib/python3.7/site-packages/qutebrowser
Using Python from /usr/bin/python3
Qt library executable path: /usr/lib/qt/libexec, data path: /usr/share/qt

Does the bug happen if you start with --temp-basedir?:

Yes

Description

The position on a webpage is sometimes properly maintained and sometimes lost when jumping back and forth between a qutebrowser window and another window.

Details

I jump very often between windows of different applications using keybindings of my window manager (EXWM).

Sometimes the scroll position on a webpage is properly maintained when I jump back to it. Sometimes it is shifted. Quite often the page is scrolled back up to the top and I have to scroll back down to where I was.

I believe that this behaviour is quite recent (maybe a month or two?). I don't remember ever having this issue before.

@jgkamat
Copy link
Member

jgkamat commented Feb 15, 2019 via email

@prosoitos
Copy link
Contributor Author

I don't know. I don't have falkon installed. If it is important, I guess I could install it.

@user202729
Copy link
Contributor

On which websites do this happen? All of them?

@The-Compiler
Copy link
Member

Would indeed be good if you could try to reproduce this with Falkon, so we know whether it's a QtWebEngine or a qutebrowser issue.

The size of the windows doesn't change when you do this, right? Not sure what's going on, I've never seen this FWIW.

@The-Compiler The-Compiler added the status: can't reproduce Issues which can't be reproduced. label Feb 16, 2019
@prosoitos
Copy link
Contributor Author

prosoitos commented Feb 16, 2019

On which websites does this happen? All of them?

Not sure whether it happens on all sites as I never really paid attention to that. The behaviour is also very inconsistant (for instance, I can do this jumping back and forth 2 or 3 times in a row fine and on the 4th time it will jump back to the top. On other occasions, it jumps back to the top right from the first time I leave the qutebrowser window and go back to it).

I know for sure that it does it on the Arch Wiki, on the Emacs Wiki, and on the LaTeX Wikibook websites because I do a lot of jumping back and forth from those sites (reading things and implementing them in my files or cli). Can't remember for sure about other sites.

The size of the windows doesn't change when you do this, right?

No, it does not. It's just that the scrolling position is lost. Most often I am back at the top, some other time I am still near-ish the area of the page where I previously was, but not the same place.

Would indeed be good if you could try to reproduce this with Falkon, so we know whether it's a QtWebEngine or a qutebrowser issue.

Ok. Will do.

@prosoitos
Copy link
Contributor Author

prosoitos commented Feb 16, 2019

Of note, in case it matters at all: I am using EXWM as my window manager. So the jumping back and forth I do is one of:

  • jumping to another workspace, then coming back to the workspace in which qutebrowser is, or

  • jumping to another window within the same workspace. EXWM is a tiling window manager and I like my windows big, so I seldom have several windows visible at once. So I jump from only qutebrowser visible in the current window to only some other app visible in the current window, and then back to only qutebrowser visible in the current window.

And I do this with Emacs keybindings. The problem can happen in both situations.

As I wrote earlier, this was not happening until some time ago.

@prosoitos
Copy link
Contributor Author

prosoitos commented Feb 16, 2019

Actually, you might have a point @user202729: I am trying to trigger the problem on various sites right now. I am failing to have it happen on this page, but it happens all the time on the Arch Wiki pages. So it seems that it does depend on the website.

That said, because of the inconsistency of the behaviour, it is hard to be sure.

edit: I was wrong. It actually does it on GitHub too. I think that it can do it on any website...

@prosoitos
Copy link
Contributor Author

prosoitos commented Feb 16, 2019

Same thing happens with Falkon...

Which is bad news for me I guess as you probably will not be able to do anything about it then. No? Should I close this issue and open an issue on the qtwebengine GitHub? edit: seems that I can't open issues there.

@prosoitos
Copy link
Contributor Author

prosoitos commented Feb 17, 2019

As a way to go back to where I was in the page faster, I tried to use set_mark and jump_mark and they work quite poorly. jump_mark gets me kind of in the area of the mark, but it is very far from accurate (I am not talking of a few lines off, but maybe a page or so). I had played with these functions a little when I first started using qutebrowser and I remember them as working much better than this.

Not sure whether there is any link with the current issue or whether I should open a new one.

edit: thanks for @codesections for pointing out that there is already an issue on this at #4013. I now have a HiDPI machine and use a default zoom of 140%. That's what is different from my early tries of these functionalities.

@jgkamat
Copy link
Member

jgkamat commented Feb 17, 2019 via email

@prosoitos
Copy link
Contributor Author

prosoitos commented Feb 18, 2019

Mmmm... to do that, I would have to install another Window Manager... I am not sure I want to get into that.

I can run qutebrowser without launching EXWM, but then I cannot jump back and forth between windows, which is what causes the problem behaviour.

And since I seem to be the only one having this problem, it might be that I would not have it with i3, openbox or whatever. It used to work just fine though (and I was already using EXWM).

@prosoitos
Copy link
Contributor Author

prosoitos commented Feb 19, 2019

No idea whether this is of any interest, but the same thing happens on a page with a pdf (with pdfjs).

@codesections
Copy link

codesections commented Feb 20, 2019

I can add an additional data point here, in case it's helpful.

I am experiencing the same issue, and it appears that it might be triggered by qutebrowser believing it has received an extra <TAB> key press, which modifies the scroll position. (Though other applications do not receive that key press when changing windows).

I can also add some info about the window manager. I do not experience the issue when using dwm, but I do when using stumpwm. Stumpwm, like exwm, is built in Lisp—I note that as an interesting commonality, though I'm not sure how it could be related.

update
I determined that, while this bug does not occur on other browsers, it does occur on webmacs, another qtwebengine browser. In addition to the testing with falcon, this strongly indicates that it is an upstream bug.

2nd update
Running qutebrowser --backend webkit is a (not great) workaround for this issue, which confirms that it's an upstream qtwebengine issue. It would be great to get an bug report opened up there, but I'm not familiar with the Qt bug process, and it looks like reporting issues is a fairly involved procedure (at least compared to opening a github/gitlab issue).

@codesections
Copy link

One possible work-around is to use set-mark and jump-mark to return to the previous scroll position (which, depending on the WM, could probably be automated with each window jump). I haven't been able to implement this work around, though, because I ran into #4013, which means that set-mark and jump-mark don't work properly for me.

@prosoitos
Copy link
Contributor Author

prosoitos commented Feb 21, 2019

Thanks for your additional info! The Lisp thing is interesting...

I am also using a default zoom different from 100% and that explains my comments above in this thread about set_mark and jump_mark working poorly. I hadn't spotted #4013. Thank you for pointing it out to me. At least, now I know why this is, that it is not related to the current issue, and that an issue is already open.

I would very much love for this to be addressed as I do a lot of this jumping back and forth and this is a rather big issue for me. I am in the same boat re-posting an issue upstream. Keen to help with the process though (if I can).

Glad to hear that I am not the only one affected. At least, your observations seem to rule out that it is something weird in my settings and indicate that it is more likely something to do with my WM (as I kind of suspected).

@user202729
Copy link
Contributor

user202729 commented Feb 21, 2019

If that's the case, bind <tab> :nop would be a possible workaround for the issue.

@prosoitos
Copy link
Contributor Author

If that's the case, bind <tab> :nop KP-FG would temporarily solve the issue.

Would you mind giving a little explanation please? Thanks!

@codesections
Copy link

If that's the case, bind <tab> :nop would be a possible workaround for the issue.

@user202729 that does solve it! Thanks so much—I don't know why that didn't occur to me.

Would you mind giving a little explanation please? Thanks!

The work-around is to re-bind the <tab> to do nothing (instead of sending a <tab> to the webpage). To do this, you can enter :bind <tab> nop in qutebrowser's command mode. ("nop" stands for "no operation", that is, do nothing.)

Equivalently, you can add the following to your .config/qutebrowser/config.py file:

c.bindings.commands = {
        'normal': {
            '<tab>': 'nop',
            }
        }

(If you have other bindings set in your config.py file, you'd need to merge this in with that config dict)

@prosoitos
Copy link
Contributor Author

prosoitos commented Feb 22, 2019

Should I understand that you are switching windows with Alt+Tab as is the case on Windows? I am still confused.

To unbind a key, one can also do:

config.unbind('<tab>', mode='normal')

But I still don't get the point (unless you use Alt+Tab to switch windows). What am I missing?

In my case, my jumping back and forth occurs with various keybindings set through EXWM and none of them involve the <tab> key.

@codesections
Copy link

Should I understand that you are switching windows with Alt+Tab as is the case on Windows? I am still confused.

No, I am not pressing tab at all to switch windows.

I am switching with <C-SPC> + a, actually, but the actual keys aren't important.

What I am saying is

it appears that [the bug] might be triggered by qutebrowser believing it has received an extra <TAB> key press, which modifies the scroll position. (Though other applications do not receive that key press when changing windows).

That is, for some reason, qutebrowser/qtwebengine is acting as though it received a <tab> press, even though we never sent one. Based on thinking it got a tab press, it is changing the scroll position of the page (just as it would if we actually pressed <tab>). Thus, the temporary work-around is to rebind <tab> to nop. That way, qutebrowser still (incorrectly) gets an extra <tab>, but that doesn't change the scroll position at all.

@prosoitos
Copy link
Contributor Author

prosoitos commented Feb 22, 2019

Oh, I see. Thank you! I had missed that comment of yours and was totally confused.

I will certainly try this!!

edit: Too soon to be sure, but it seems to have solved it. Thank you sincerely @codesections for pinpointing the problem to an extra TAB sent somehow, to explain to me the advice of @user202729 which I failed to understand (having missed your previous comment) and to explain how to add this to my config file.

Seems that this is also solving #4600 🙂

And thanks @user202729 for suggesting workarounds for both problems!!

@prosoitos
Copy link
Contributor Author

Out of curiosity @codesections, you don't happen to have any issues on Twitter (if you use that site)? #4108 is closed but I still have the problem. I wonder now whether it could be some other keys being sent when they shouldn't and if it isn't a similar sort of problem...

@codesections
Copy link

I don't use Twitter, and haven't encountered any issue like that on Mastodon (the Twitter-like site that I do use). Sounds pretty odd!

@prosoitos
Copy link
Contributor Author

Closing this as the workaround works (even if it is still unclear what is really going on). It also solved #4600 and #4108.

@prosoitos
Copy link
Contributor Author

Sounds pretty odd!

Yes, it was totally crazy. But the workaround for this issue actually solved that too (even if the behaviour was very different than what TAB would do, which is quite puzzling).

@The-Compiler The-Compiler added the component: keyinput Issues related to processing keypresses. label Feb 22, 2019
@The-Compiler The-Compiler added priority: 0 - high Issues which are currently the primary focus. qt: 5.13 Issues related to Qt 5.13. labels Jul 22, 2019
@amenl
Copy link

amenl commented Oct 9, 2019

Here is another finding to that issue.
The tab problem doesn't occur when I open a link/quickmark in a new tab (this was known). But it also doesn't occur when I open the same link in the same tab, whereas if it's a different link, the tab gets sent (which in my case results in tab-next, kind of annoying).

@The-Compiler
Copy link
Member

Can people who still see this mention on which Qt versions you are? I still suspect this is due to QTBUG-76268 which means it should be fixed in Qt 5.12.5, 5.13.1 and 5.14.

@amenl
Copy link

amenl commented Oct 9, 2019

Most qt5 packages I see show version 5.13.1-1 (arch linux), upgraded some days ago. the problem persists.

@arza-zara
Copy link
Member

On archlinux with openbox, upgrading qt5-webengine 5.13.0-4 -> 5.13.1-1 fixes it for me.

@dschrempf
Copy link

I am also on archlinux, the problem seems to be gone! Version 5.13.1-3.

@amenl
Copy link

amenl commented Oct 9, 2019

I tried every possible qt5-webengine package in the last months and reinstalled all of the packages to no avail. The only thing that works is using webkit as backend. I'm not sure if this is a good solution.
It is a bit unsatisfying.

@amenl
Copy link

amenl commented Oct 11, 2019

What's interesting...

I keep 3 machines (all Arch Linux) in sync (config + most packages). All 3 are up to date and show the same erroneous bahaviour.

The only changes that work are following:

  • uncommenting tab key binding
    config.bind('<Tab>', 'tab-next')
  • setting webkit backend
    config.set('backend', 'webkit')

So I encourage everyone to bind Tab to tab-next and check the result.
(erroneous result would occur when opening a link with a different domain name (!) in the same tab would trigger a tab-next action)

@PluMGMK
Copy link

PluMGMK commented Oct 26, 2019

Guys, this is kind of embarrassing, but I figured out what's causing it in my case, and it was kind of obvious. I have had "statusbar.hide" on for years now, and disabling it got rid of this problem.
Seems that the act of hiding the statusbar, when a command is finished, automatically sends a tab to WebEngine…

@The-Compiler
Copy link
Member

@PluMGMK You might be seeing #2236.

@amenl
Copy link

amenl commented Oct 26, 2019

@PluMGMK Makes no difference for me. Still same ol'..

@PluMGMK
Copy link

PluMGMK commented Oct 26, 2019

Yeah, looks like I've been following the wrong issue all along. One would like to think they're related though…

@The-Compiler
Copy link
Member

From Reddit, this apparently seems to (differently) affect newer Qt versions as well:

https://www.reddit.com/r/qutebrowser/comments/fkohq7/return_triggers_tabnext_after_keybind_to_tab/

@adigitoleo
Copy link

Hi,

So I just switched from i3 to bspwm and am now experiencing this as well. It wasn't happening on i3 (i3-gaps). I think it's only happening in normal mode. Happens when showing the previously hidden qutebrowser window in any way (mod+, rofi, etc.). I have bound to tab-next so it's a bit disorienting.

My qutebrowser info
qutebrowser v1.13.1
Git commit: 
Backend: QtWebEngine (Chromium 80.0.3987.163)
Qt: 5.15.1

CPython: 3.8.5
PyQt: 5.15.1

sip: 5.4.0
colorama: 0.4.3
pypeg2: 2.15
jinja2: 2.11.2
pygments: 2.7.1
yaml: 5.3.1
cssutils: no
attr: 20.2.0
PyQt5.QtWebEngineWidgets: yes
PyQt5.QtWebEngine: 5.15.1
PyQt5.QtWebKitWidgets: no
pdf.js: no
sqlite: 3.33.0
QtNetwork SSL: OpenSSL 1.1.1h  22 Sep 2020

Style: Kvantum::Style
Platform plugin: xcb
OpenGL: Intel, 4.6 (Compatibility Profile) Mesa 20.1.8
Platform: Linux-5.8.10-arch1-1-x86_64-with-glibc2.2.5, 64bit
Linux distribution: Arch Linux (arch)
Frozen: False
Imported from /usr/lib/python3.8/site-packages/qutebrowser
Using Python from /usr/bin/python3
Qt library executable path: /usr/lib/qt/libexec, data path: /usr/share/qt

Paths:
cache: /home/leon/.cache/qutebrowser
config: /home/leon/.config/qutebrowser
data: /home/leon/.local/share/qutebrowser
runtime: /run/user/1000/qutebrowser
system data: /usr/share/qutebrowser

Autoconfig loaded: no
Config.py: /home/leon/.config/qutebrowser/config.py has been loaded
Uptime: 0:14:52

@toofar
Copy link
Member

toofar commented Mar 17, 2023

I think we can remove these with a patch like this:

diff --git i/qutebrowser/keyinput/eventfilter.py w/qutebrowser/keyinput/eventfilter.py
index 31ffcc7f9b34..30737090d19f 100644
--- i/qutebrowser/keyinput/eventfilter.py
+++ w/qutebrowser/keyinput/eventfilter.py
@@ -85,12 +85,17 @@ class EventFilter(QObject):
 
         Return:
             True if the event should be filtered, False if it's passed through.
         """
         if not isinstance(obj, QWindow):
             # We already handled this same event at some point earlier, so
             # we're not interested in it anymore.
             return False
 
+        if not obj.isVisible() and obj.objectName() == 'QQuickWidgetOffscreenWindow':
+            print(f"Ignoring event from QtQuick offscreen window {event.type()=}")
+            return False
+
         typ = event.type()
 
         if typ not in self._handlers:

Because the KeyEvents (which are the only ones we are handling here) are being sent to the offscreen window. We probably don't need to be handling key events to that? We are only getting them because we have a global event filter.

I'm not sure if we should be returning False here or just not calling out to our handlers.

@The-Compiler
Copy link
Member

I think this was fixed in Qt at some point. Can't reproduce anymore even with Qt 5: python3 -m qutebrowser -s qt.chromium.sandboxing disable-all --qt-wrapper PyQt5 --temp-basedir ':bind <Tab> message-info foo'

The-Compiler added a commit that referenced this issue Dec 7, 2023
Almost 7 years ago, it was observed that hiding the status bar causes some
websites being scrolled to the top: #2236.

Back then, it never really was clear why this happens. However, with the v3.0.0
release, we had a regression causing the same thing to happen when leaving
prompt mode: #7885.

Thanks to "git bisect", the culprit was found to be 8e152aa, "Don't give
keyboard focus to tab bar", which was a fix for #7820. However, it still wasn't
clear why this phenomenon happens.

What made things clearer to me was a combination of debugging and an old comment
by pevu: #2236 (comment)

    Chromium-browser has the same issue. When you open lipsum.com, scroll down,
    then focus the location bar (url box), then press Tab, it will jump to the
    top of the page and focus the first link. This doesn't happen when you
    switch focus using the mouse.

    It seems to be an issue of how the view containing the website is focused
    when all qutebrowser ui elements disappear.

And indeed, tabbing into the web contents from the UI elements via the tab key
in Chromium causes the website to start at the top, presumably as an
accessibility feature?

Essentially, this is also what happens in qutebrowser when an UI element is
hidden while it still has focus: In QWidget::hide() (or, rather,
QWidgetPrivate::hide_helper()), Qt moves the focus to the next widget by
calling focusPrevNextChild(true):
https://github.com/qt/qtbase/blob/v6.6.1/src/widgets/kernel/qwidget.cpp#L8259-L8271

And apparently, focusPrevNextChild() basically does the same thing as pressing
the tab key, to the point that there is some code in Qt Declarative actually
making tab keypresses out of it (which I'm still not sure is related, or maybe
just the cause of #4579):
https://github.com/qt/qtdeclarative/blob/v6.6.1/src/quickwidgets/qquickwidget.cpp#L1415-L1429

jome debugging confirms that this is exactly what happening:

1) We hide the status bar (or prompt) which has keyboard focus
2) Qt focuses the web view, which triggers the Chromium feature (?) scrolling it
   to the very top.
3) Only then, in TabbedBrowser.on_mod_left(), we noticed that the command or
   prompt mode was left, and reassign focus to the web view properly.

In step 2), before this change, Qt happened to focus the tab bar (before we set
the focus manually to the web contents), and thus this didn't happen.
Not sure why it didn't focus the tab bar when we hid the status bar (maybe
because how our widget hierarchy works with TabbedBrowser?).

Python stacktrace of hiding prompt:

    Traceback (most recent call first):
    <built-in method hide of DownloadFilenamePrompt object at remote 0x7fffb8bc65f0>
    File ".../qutebrowser/mainwindow/prompt.py", line 204, in _on_mode_left
        self.show_prompts.emit(None)
    File ".../qutebrowser/keyinput/modeman.py", line 434, in leave
        self.left.emit(mode, self.mode, self._win_id)
    File ".../qutebrowser/keyinput/modeman.py", line 445, in mode_leave
        self.leave(self.mode, 'leave current')

C++ stacktrace, with the focus change presumably being passed of to Chromium
here: https://github.com/qt/qtwebengine/blob/dev/src/core/render_widget_host_view_qt_delegate_client.cpp#L714

    #0  QtWebEngineCore::RenderWidgetHostViewQtDelegateClient::handleFocusEvent(QFocusEvent*) () at /usr/src/debug/qt6-webengine/qtwebengine-everywhere-src-6.6.0/src/core/render_widget_host_view_qt_delegate_client.cpp:708
    #1  QtWebEngineCore::RenderWidgetHostViewQtDelegateClient::handleFocusEvent(QFocusEvent*) () at /usr/src/debug/qt6-webengine/qtwebengine-everywhere-src-6.6.0/src/core/render_widget_host_view_qt_delegate_client.cpp:705
    #2  0x00007fffe5fea70c in QtWebEngineCore::RenderWidgetHostViewQtDelegateClient::forwardEvent(QEvent*) () at /usr/src/debug/qt6-webengine/qtwebengine-everywhere-src-6.6.0/src/core/render_widget_host_view_qt_delegate_client.cpp:300
    #3  0x00007fffe4dd5c79 in QQuickItem::event(QEvent*) (this=0x555556b6cd20, ev=0x7fffffffa320) at /usr/src/debug/qt6-declarative/qtdeclarative-everywhere-src-6.6.0/src/quick/items/qquickitem.cpp:8871
    #4  0x00007ffff1f7318b in QApplicationPrivate::notify_helper(QObject*, QEvent*) (this=<optimized out>, receiver=0x555556b6cd20, e=0x7fffffffa320)
        at /usr/src/debug/qt6-base/qtbase-everywhere-src-6.6.0/src/widgets/kernel/qapplication.cpp:3290
    #5  0x00007ffff295e4a7 in  () at /usr/lib/python3.11/site-packages/PyQt6/QtWidgets.abi3.so
    #6  0x00007ffff59626d8 in QCoreApplication::notifyInternal2(QObject*, QEvent*) (receiver=0x555556b6cd20, event=0x7fffffffa320) at /usr/src/debug/qt6-base/qtbase-everywhere-src-6.6.0/src/corelib/kernel/qcoreapplication.cpp:1118
    #7  0x00007ffff596271d in QCoreApplication::sendEvent(QObject*, QEvent*) (receiver=<optimized out>, event=<optimized out>) at /usr/src/debug/qt6-base/qtbase-everywhere-src-6.6.0/src/corelib/kernel/qcoreapplication.cpp:1536
    #8  0x00007fffe4f33f15 in QQuickDeliveryAgentPrivate::setFocusInScope(QQuickItem*, QQuickItem*, Qt::FocusReason, QFlags<QQuickDeliveryAgentPrivate::FocusOption>)
        (this=<optimized out>, scope=<optimized out>, item=<optimized out>, reason=<optimized out>, options=...) at /usr/src/debug/qt6-declarative/qtdeclarative-everywhere-src-6.6.0/src/quick/util/qquickdeliveryagent.cpp:439
    #9  0x00007fffe4dd348a in QQuickItem::setFocus(bool, Qt::FocusReason) (this=0x555556b724d0, focus=<optimized out>, reason=Qt::TabFocusReason) at /usr/include/qt6/QtCore/qflags.h:73
    #10 0x00007fffe4e7239b in QQuickWindow::focusInEvent(QFocusEvent*) (this=<optimized out>, ev=<optimized out>) at /usr/src/debug/qt6-declarative/qtdeclarative-everywhere-src-6.6.0/src/quick/items/qquickwindow.cpp:231
    #11 0x00007ffff1fc3a05 in QWidget::event(QEvent*) (this=0x555556457b50, event=0x7fffffffa770) at /usr/src/debug/qt6-base/qtbase-everywhere-src-6.6.0/src/widgets/kernel/qwidget.cpp:9111
    #12 0x00007ffff1f7318b in QApplicationPrivate::notify_helper(QObject*, QEvent*) (this=<optimized out>, receiver=0x555556457b50, e=0x7fffffffa770)
        at /usr/src/debug/qt6-base/qtbase-everywhere-src-6.6.0/src/widgets/kernel/qapplication.cpp:3290
    #13 0x00007ffff295e4a7 in  () at /usr/lib/python3.11/site-packages/PyQt6/QtWidgets.abi3.so
    #14 0x00007ffff59626d8 in QCoreApplication::notifyInternal2(QObject*, QEvent*) (receiver=0x555556457b50, event=0x7fffffffa770) at /usr/src/debug/qt6-base/qtbase-everywhere-src-6.6.0/src/corelib/kernel/qcoreapplication.cpp:1118
    #15 0x00007ffff596271d in QCoreApplication::sendEvent(QObject*, QEvent*) (receiver=<optimized out>, event=<optimized out>) at /usr/src/debug/qt6-base/qtbase-everywhere-src-6.6.0/src/corelib/kernel/qcoreapplication.cpp:1536
    #16 0x00007ffff1f7f1b2 in QApplicationPrivate::setFocusWidget(QWidget*, Qt::FocusReason) (focus=0x555556457b50, reason=<optimized out>) at /usr/src/debug/qt6-base/qtbase-everywhere-src-6.6.0/src/widgets/kernel/qapplication.cpp:1538
    #17 0x00007ffff1fca29d in QWidget::setFocus(Qt::FocusReason) (this=0x555556b1ceb0, reason=<optimized out>) at /usr/src/debug/qt6-base/qtbase-everywhere-src-6.6.0/src/widgets/kernel/qwidget.cpp:6580
    #18 0x00007ffff1fb4f1b in QWidget::focusNextPrevChild(bool) (this=<optimized out>, next=<optimized out>) at /usr/src/debug/qt6-base/qtbase-everywhere-src-6.6.0/src/widgets/kernel/qwidget.cpp:6844
    #19 0x00007ffff298d0ac in  () at /usr/lib/python3.11/site-packages/PyQt6/QtWidgets.abi3.so
    #20 0x00007ffff298d0ac in  () at /usr/lib/python3.11/site-packages/PyQt6/QtWidgets.abi3.so
    #21 0x00007ffff298d0ac in  () at /usr/lib/python3.11/site-packages/PyQt6/QtWidgets.abi3.so
    #22 0x00007ffff1fbdb76 in QWidgetPrivate::hide_helper() (this=this@entry=0x55555646a360) at /usr/src/debug/qt6-base/qtbase-everywhere-src-6.6.0/src/widgets/kernel/qwidget.cpp:8271
    #23 0x00007ffff1fbf158 in QWidgetPrivate::setVisible(bool) (this=0x55555646a360, visible=<optimized out>) at /usr/src/debug/qt6-base/qtbase-everywhere-src-6.6.0/src/widgets/kernel/qwidget.cpp:8447
    [...]

We fix this problem by explicitly handling focus before hiding the UI elements.
This is done with a new TabbedBrowser.on_release_focus() slot, which is bound to
signals emitted just before things are hidden: The existing Command.hide_cmd()
for the status bar, and a new release_focus() signal for prompts.

Additionally, we make sure to not double-handle hiding in the statusbar
code when it's already handled separately for comamnd mode.

Unfortunately, no tests for this, as application window focus is required to
reproduce the issue. In theory, a test in scroll.feature could be added though,
which loads simple.html, scrolls down, shows/hides a prompt or the status bar,
and then checks the vertical scroll position is != 0.

Fixes #2236
Fixes #7885
The-Compiler added a commit that referenced this issue Dec 7, 2023
Almost 7 years ago, it was observed that hiding the status bar causes some
websites being scrolled to the top: #2236.

Back then, it never really was clear why this happens. However, with the v3.0.0
release, we had a regression causing the same thing to happen when leaving
prompt mode: #7885.

Thanks to "git bisect", the culprit was found to be 8e152aa, "Don't give
keyboard focus to tab bar", which was a fix for #7820. However, it still wasn't
clear why this phenomenon happens.

What made things clearer to me was a combination of debugging and an old comment
by pevu: #2236 (comment)

> Chromium-browser has the same issue. When you open lipsum.com, scroll down,
> then focus the location bar (url box), then press Tab, it will jump to the
> top of the page and focus the first link. This doesn't happen when you
> switch focus using the mouse.
>
> It seems to be an issue of how the view containing the website is focused
> when all qutebrowser ui elements disappear.

And indeed, tabbing into the web contents from the UI elements via the tab key
in Chromium causes the website to start at the top, presumably as an
accessibility feature?

Essentially, this is also what happens in qutebrowser when an UI element is
hidden while it still has focus: In QWidget::hide() (or, rather,
QWidgetPrivate::hide_helper()), Qt moves the focus to the next widget by
calling focusPrevNextChild(true):
https://github.com/qt/qtbase/blob/v6.6.1/src/widgets/kernel/qwidget.cpp#L8259-L8271

And apparently, focusPrevNextChild() basically does the same thing as pressing
the tab key, to the point that there is some code in Qt Declarative actually
making tab keypresses out of it (which I'm still not sure is related, or maybe
just the cause of #4579):
https://github.com/qt/qtdeclarative/blob/v6.6.1/src/quickwidgets/qquickwidget.cpp#L1415-L1429

Some debugging confirms that this is exactly what happening:

1) We hide the status bar (or prompt) which has keyboard focus
2) Qt focuses the web view, which triggers the Chromium feature (?) scrolling it
   to the very top.
3) Only then, in TabbedBrowser.on_mod_left(), we noticed that the command or
   prompt mode was left, and reassign focus to the web view properly.

In step 2), before this change, Qt happened to focus the tab bar (before we set
the focus manually to the web contents), and thus this didn't happen.
Not sure why it didn't focus the tab bar when we hid the status bar (maybe
because how our widget hierarchy works with TabbedBrowser?).

Python stacktrace of hiding prompt:

    Traceback (most recent call first):
    <built-in method hide of DownloadFilenamePrompt object at remote 0x7fffb8bc65f0>
    File ".../qutebrowser/mainwindow/prompt.py", line 204, in _on_mode_left
        self.show_prompts.emit(None)
    File ".../qutebrowser/keyinput/modeman.py", line 434, in leave
        self.left.emit(mode, self.mode, self._win_id)
    File ".../qutebrowser/keyinput/modeman.py", line 445, in mode_leave
        self.leave(self.mode, 'leave current')

C++ stacktrace, with the focus change presumably being passed of to Chromium
here: https://github.com/qt/qtwebengine/blob/dev/src/core/render_widget_host_view_qt_delegate_client.cpp#L714

    #0  QtWebEngineCore::RenderWidgetHostViewQtDelegateClient::handleFocusEvent(QFocusEvent*) () at /usr/src/debug/qt6-webengine/qtwebengine-everywhere-src-6.6.0/src/core/render_widget_host_view_qt_delegate_client.cpp:708
    #1  QtWebEngineCore::RenderWidgetHostViewQtDelegateClient::handleFocusEvent(QFocusEvent*) () at /usr/src/debug/qt6-webengine/qtwebengine-everywhere-src-6.6.0/src/core/render_widget_host_view_qt_delegate_client.cpp:705
    #2  0x00007fffe5fea70c in QtWebEngineCore::RenderWidgetHostViewQtDelegateClient::forwardEvent(QEvent*) () at /usr/src/debug/qt6-webengine/qtwebengine-everywhere-src-6.6.0/src/core/render_widget_host_view_qt_delegate_client.cpp:300
    #3  0x00007fffe4dd5c79 in QQuickItem::event(QEvent*) (this=0x555556b6cd20, ev=0x7fffffffa320) at /usr/src/debug/qt6-declarative/qtdeclarative-everywhere-src-6.6.0/src/quick/items/qquickitem.cpp:8871
    #4  0x00007ffff1f7318b in QApplicationPrivate::notify_helper(QObject*, QEvent*) (this=<optimized out>, receiver=0x555556b6cd20, e=0x7fffffffa320)
        at /usr/src/debug/qt6-base/qtbase-everywhere-src-6.6.0/src/widgets/kernel/qapplication.cpp:3290
    #5  0x00007ffff295e4a7 in  () at /usr/lib/python3.11/site-packages/PyQt6/QtWidgets.abi3.so
    #6  0x00007ffff59626d8 in QCoreApplication::notifyInternal2(QObject*, QEvent*) (receiver=0x555556b6cd20, event=0x7fffffffa320) at /usr/src/debug/qt6-base/qtbase-everywhere-src-6.6.0/src/corelib/kernel/qcoreapplication.cpp:1118
    #7  0x00007ffff596271d in QCoreApplication::sendEvent(QObject*, QEvent*) (receiver=<optimized out>, event=<optimized out>) at /usr/src/debug/qt6-base/qtbase-everywhere-src-6.6.0/src/corelib/kernel/qcoreapplication.cpp:1536
    #8  0x00007fffe4f33f15 in QQuickDeliveryAgentPrivate::setFocusInScope(QQuickItem*, QQuickItem*, Qt::FocusReason, QFlags<QQuickDeliveryAgentPrivate::FocusOption>)
        (this=<optimized out>, scope=<optimized out>, item=<optimized out>, reason=<optimized out>, options=...) at /usr/src/debug/qt6-declarative/qtdeclarative-everywhere-src-6.6.0/src/quick/util/qquickdeliveryagent.cpp:439
    #9  0x00007fffe4dd348a in QQuickItem::setFocus(bool, Qt::FocusReason) (this=0x555556b724d0, focus=<optimized out>, reason=Qt::TabFocusReason) at /usr/include/qt6/QtCore/qflags.h:73
    #10 0x00007fffe4e7239b in QQuickWindow::focusInEvent(QFocusEvent*) (this=<optimized out>, ev=<optimized out>) at /usr/src/debug/qt6-declarative/qtdeclarative-everywhere-src-6.6.0/src/quick/items/qquickwindow.cpp:231
    #11 0x00007ffff1fc3a05 in QWidget::event(QEvent*) (this=0x555556457b50, event=0x7fffffffa770) at /usr/src/debug/qt6-base/qtbase-everywhere-src-6.6.0/src/widgets/kernel/qwidget.cpp:9111
    #12 0x00007ffff1f7318b in QApplicationPrivate::notify_helper(QObject*, QEvent*) (this=<optimized out>, receiver=0x555556457b50, e=0x7fffffffa770)
        at /usr/src/debug/qt6-base/qtbase-everywhere-src-6.6.0/src/widgets/kernel/qapplication.cpp:3290
    #13 0x00007ffff295e4a7 in  () at /usr/lib/python3.11/site-packages/PyQt6/QtWidgets.abi3.so
    #14 0x00007ffff59626d8 in QCoreApplication::notifyInternal2(QObject*, QEvent*) (receiver=0x555556457b50, event=0x7fffffffa770) at /usr/src/debug/qt6-base/qtbase-everywhere-src-6.6.0/src/corelib/kernel/qcoreapplication.cpp:1118
    #15 0x00007ffff596271d in QCoreApplication::sendEvent(QObject*, QEvent*) (receiver=<optimized out>, event=<optimized out>) at /usr/src/debug/qt6-base/qtbase-everywhere-src-6.6.0/src/corelib/kernel/qcoreapplication.cpp:1536
    #16 0x00007ffff1f7f1b2 in QApplicationPrivate::setFocusWidget(QWidget*, Qt::FocusReason) (focus=0x555556457b50, reason=<optimized out>) at /usr/src/debug/qt6-base/qtbase-everywhere-src-6.6.0/src/widgets/kernel/qapplication.cpp:1538
    #17 0x00007ffff1fca29d in QWidget::setFocus(Qt::FocusReason) (this=0x555556b1ceb0, reason=<optimized out>) at /usr/src/debug/qt6-base/qtbase-everywhere-src-6.6.0/src/widgets/kernel/qwidget.cpp:6580
    #18 0x00007ffff1fb4f1b in QWidget::focusNextPrevChild(bool) (this=<optimized out>, next=<optimized out>) at /usr/src/debug/qt6-base/qtbase-everywhere-src-6.6.0/src/widgets/kernel/qwidget.cpp:6844
    #19 0x00007ffff298d0ac in  () at /usr/lib/python3.11/site-packages/PyQt6/QtWidgets.abi3.so
    #20 0x00007ffff298d0ac in  () at /usr/lib/python3.11/site-packages/PyQt6/QtWidgets.abi3.so
    #21 0x00007ffff298d0ac in  () at /usr/lib/python3.11/site-packages/PyQt6/QtWidgets.abi3.so
    #22 0x00007ffff1fbdb76 in QWidgetPrivate::hide_helper() (this=this@entry=0x55555646a360) at /usr/src/debug/qt6-base/qtbase-everywhere-src-6.6.0/src/widgets/kernel/qwidget.cpp:8271
    #23 0x00007ffff1fbf158 in QWidgetPrivate::setVisible(bool) (this=0x55555646a360, visible=<optimized out>) at /usr/src/debug/qt6-base/qtbase-everywhere-src-6.6.0/src/widgets/kernel/qwidget.cpp:8447
    [...]

We fix this problem by explicitly handling focus before hiding the UI elements.
This is done with a new TabbedBrowser.on_release_focus() slot, which is bound to
signals emitted just before things are hidden: The existing Command.hide_cmd()
for the status bar, and a new release_focus() signal for prompts.

Additionally, we make sure to not double-handle hiding in the statusbar
code when it's already handled separately for comamnd mode.

Unfortunately, no tests for this, as application window focus is required to
reproduce the issue. In theory, a test in scroll.feature could be added though,
which loads simple.html, scrolls down, shows/hides a prompt or the status bar,
and then checks the vertical scroll position is != 0.

Fixes #2236
Fixes #7885
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug: behavior Something doesn't work as intended, but doesn't crash. component: keyinput Issues related to processing keypresses. component: QtWebEngine Issues related to the QtWebEngine backend, based on Chromium. priority: 0 - high Issues which are currently the primary focus.
Projects
None yet
Development

No branches or pull requests