Repository: qutebrowser/qutebrowser

Issue:
Statusbar gets confused when "f V" is pressed too quickly
On cee5d0b0d with Qt 5.15.0, running `:fake-key -g fV` and then esc ends up in a state where there is a purple status bar but we're not actually in caret mode.

Logs:

```
14:33:32 DEBUG    commands   command:run:542 command called: fake-key ['-g', 'fV']
14:33:32 DEBUG    commands   command:run:556 Calling qutebrowser.browser.commands.CommandDispatcher.fake_key(<qutebrowser.browser.commands.CommandDispatcher>, 'fV', True)
14:33:32 DEBUG    modes      modeman:_handle_keypress:306 match: 2, forward_unbound_keys: auto, passthrough: True, is_non_alnum: True, dry_run: False --> filter: True (focused: <PyQt5.QtWidgets.QWidget object at 0x7f9060079c10>)
14:33:32 DEBUG    modes      modeman:_handle_keypress:281 got keypress in mode KeyMode.normal - delegating to <qutebrowser.keyinput.modeparsers.NormalKeyParser>
14:33:32 DEBUG    keyboard   basekeyparser:_debug_log:218 NormalKeyParser for mode normal: Got key: 0x46 / modifiers: 0x0 / text: 'f' / dry_run False
14:33:32 DEBUG    keyboard   basekeyparser:_debug_log:218 NormalKeyParser for mode normal: Definitive match for 'f'.
14:33:32 DEBUG    keyboard   basekeyparser:_debug_log:218 NormalKeyParser for mode normal: Clearing keystring (was: f).
14:33:32 DEBUG    commands   command:run:542 command called: hint
14:33:32 DEBUG    commands   command:run:556 Calling qutebrowser.browser.hints.HintManager.start(<qutebrowser.browser.hints.HintManager object at 0x7f906015adc0>, 'all', <Target.normal: 1>, mode=None, add_history=False, rapid=False, first=False)
14:33:32 DEBUG    modes      modeman:_handle_keypress:306 match: 2, forward_unbound_keys: auto, passthrough: False, is_non_alnum: False, dry_run: False --> filter: True (focused: <PyQt5.QtWidgets.QWidget object at 0x7f9060079af0>)
14:33:32 DEBUG    modes      modeman:_handle_keyrelease:331 filter: True
14:33:32 DEBUG    modes      modeman:_handle_keypress:281 got keypress in mode KeyMode.normal - delegating to <qutebrowser.keyinput.modeparsers.NormalKeyParser>
14:33:32 DEBUG    keyboard   basekeyparser:_debug_log:218 NormalKeyParser for mode normal: Got key: 0x56 / modifiers: 0x2000000 / text: 'V' / dry_run False
14:33:32 DEBUG    keyboard   basekeyparser:_debug_log:218 NormalKeyParser for mode normal: Definitive match for 'V'.
14:33:32 DEBUG    keyboard   basekeyparser:_debug_log:218 NormalKeyParser for mode normal: Clearing keystring (was: V).
14:33:32 DEBUG    commands   command:run:542 command called: enter-mode ['caret']
14:33:32 DEBUG    commands   command:run:556 Calling qutebrowser.keyinput.modeman.ModeManager.enter_mode(<qutebrowser.keyinput.modeman.ModeManager mode=<KeyMode.normal: 1>>, 'caret')
14:33:32 DEBUG    modes      modeman:enter:357 Entering mode KeyMode.caret (reason: command)
14:33:32 DEBUG    statusbar  text:set_text:56 Setting normal text to '-- CARET MODE --'.
14:33:32 DEBUG    commands   command:run:542 command called: toggle-selection ['--line']
14:33:32 DEBUG    commands   command:run:556 Calling qutebrowser.components.caretcommands.toggle_selection(<qutebrowser.browser.webengine.webenginetab.WebEngineTab tab_id=0 url='https://start.duckduckgo.com/'>, True)
14:33:32 DEBUG    modes      modeman:_handle_keypress:306 match: 2, forward_unbound_keys: auto, passthrough: False, is_non_alnum: False, dry_run: False --> filter: True (focused: <PyQt5.QtWidgets.QWidget object at 0x7f9060079e50>)
14:33:32 DEBUG    modes      modeman:_handle_keyrelease:331 filter: False
14:33:32 DEBUG    hints      hints:_start_cb:625 hints: a, s, d, f, g, h, j
14:33:33 DEBUG    message    message:set_text:287 Follow hint...
14:33:33 DEBUG    statusbar  text:set_text:56 Setting normal text to 'Follow hint...'.
14:33:33 DEBUG    modes      modeman:enter:357 Entering mode KeyMode.hint (reason: HintManager.start)
14:33:33 DEBUG    modes      modeman:enter:372 Overriding mode KeyMode.caret.
14:33:33 DEBUG    statusbar  text:set_text:56 Setting normal text to ''.
14:33:33 DEBUG    statusbar  bar:set_mode_active:314 Setting caret mode off
14:33:33 DEBUG    webview    webenginetab:_selection_cb:393 Ignoring selection cb due to mode change.
14:33:33 DEBUG    signals    signalfilter:_filter_signals:87 emitting: cur_caret_selection_toggled(<SelectionState.line: 3>) (tab 0)
14:33:33 DEBUG    statusbar  bar:on_caret_selection_toggled:397 Setting caret selection SelectionState.line
14:33:33 DEBUG    statusbar  text:set_text:56 Setting normal text to '-- CARET LINE SELECTION MODE --'.
14:33:33 DEBUG    modes      modeman:_handle_keyrelease:331 filter: True
14:33:34 DEBUG    modes      modeman:_handle_keypress:281 got keypress in mode KeyMode.hint - delegating to <qutebrowser.keyinput.modeparsers.HintKeyParser do_log=True mode=<KeyMode.hint: 2> passthrough=False supports_count=False win_id=0>
14:33:34 DEBUG    keyboard   basekeyparser:_debug_log:218 HintKeyParser for mode hint: Got key: 0x1000000 / modifiers: 0x0 / text: '<Escape>' / dry_run True
14:33:34 DEBUG    modes      modeman:_handle_keypress:306 match: 2, forward_unbound_keys: auto, passthrough: False, is_non_alnum: False, dry_run: True --> filter: True (focused: <PyQt5.QtWidgets.QWidget object at 0x7f90600791f0>)
14:33:34 DEBUG    modes      modeman:_handle_keypress:281 got keypress in mode KeyMode.hint - delegating to <qutebrowser.keyinput.modeparsers.HintKeyParser do_log=True mode=<KeyMode.hint: 2> passthrough=False supports_count=False win_id=0>
14:33:34 DEBUG    keyboard   basekeyparser:_debug_log:218 CommandKeyParser for mode hint: Got key: 0x1000000 / modifiers: 0x0 / text: '<Escape>' / dry_run True
14:33:34 DEBUG    keyboard   modeparsers:handle:209 Handling key via command parser
14:33:34 DEBUG    keyboard   basekeyparser:_debug_log:218 CommandKeyParser for mode hint: Got key: 0x1000000 / modifiers: 0x0 / text: '<Escape>' / dry_run False
14:33:34 DEBUG    keyboard   basekeyparser:_debug_log:218 CommandKeyParser for mode hint: Definitive match for '<Escape>'.
14:33:34 DEBUG    keyboard   basekeyparser:_debug_log:218 CommandKeyParser for mode hint: Clearing keystring (was: <Escape>).
14:33:34 DEBUG    commands   command:run:542 command called: leave-mode
14:33:34 DEBUG    commands   command:run:556 Calling qutebrowser.keyinput.modeman.ModeManager.leave_current_mode(<qutebrowser.keyinput.modeman.ModeManager mode=<KeyMode.hint: 2>>)
14:33:34 DEBUG    modes      modeman:leave:424 Leaving mode KeyMode.hint (reason: leave current)
14:33:34 DEBUG    statusbar  text:maybe_reset_text:73 Ignoring reset: 'Follow hint...'
14:33:34 DEBUG    modes      modeman:_handle_keypress:306 match: 2, forward_unbound_keys: auto, passthrough: False, is_non_alnum: False, dry_run: False --> filter: True (focused: <PyQt5.QtWidgets.QWidget object at 0x7f90600791f0>)
14:33:34 DEBUG    misc       app:on_focus_object_changed:524 Focus object changed: <PyQt5.QtWidgets.QWidget object at 0x7f9060079c10>
14:33:34 DEBUG    modes      modeman:_handle_keyrelease:331 filter: True
14:33:36 DEBUG    modes      modeman:_handle_keypress:281 got keypress in mode KeyMode.normal - delegating to <qutebrowser.keyinput.modeparsers.NormalKeyParser>
```


Discussion:
Duplicate of https://github.com/qutebrowser/qutebrowser/issues/3007?

PS:
> Well, "don't do that".   &nbsp; – https://github.com/qutebrowser/qutebrowser/issues/3670#issuecomment-370244862
I think commands "not working" in some sane way is less worse than ending up in an inconsistent state, FWIW.

Resolving patch:
diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py
--- a/qutebrowser/browser/webengine/webenginetab.py
+++ b/qutebrowser/browser/webengine/webenginetab.py
@@ -531,6 +531,11 @@ def _js_call(self, command, *args, callback=None):
         self._tab.run_js_async(code, callback)
 
     def _toggle_sel_translate(self, state_str):
+        if self._mode_manager.mode != usertypes.KeyMode.caret:
+            # This may happen if the user switches to another mode after
+            # `:toggle-selection` is executed and before this callback function
+            # is asynchronously called.
+            return
         if state_str is None:
             message.error("Error toggling caret selection")
             return


Test patch: