Repository: qutebrowser/qutebrowser

Issue:
Search result nav direction does not change when searching for the same string again
**Version info**:
<!-- Please copy the first block from :version, not just the qutebrowser version -->
qutebrowser v1.14.0
Git commit: 
Backend: QtWebEngine (Chromium 83.0.4103.122)
Qt: 5.15.2

**Does the bug happen if you start with `--temp-basedir`?**:

No, because the bug only occurs when `search.incremental` is false, but it is true by default. Changing just this setting to be false causes the issue to occur.

**Description**

When performing a forward search, using `/`, then searching for the exact same string backward, using `?`, pressing `n` to go to the next result continues to move forward instead of moving backward. The reverse also happens if you start with `?` and then switch to `/`. This only happens when `search.incremental` is set to false.

**How to reproduce**
1. Set `search.incremental` to be false
2. Open a webpage and press `/` to search for a string that appears more than once on that page (e.g., search `qute` on https://github.com/qutebrowser/qutebrowser).
3. Press `n` several times to confirm that the result navigation direction is forwards.
4. Press `?` and enter the same string.
5. Press `n`, and you should see that the result navigation direction does not change to backwards, but continues forwards.


Discussion:
Thanks for the report - I can reproduce with `qutebrowser --debug --temp-basedir -s search.incremental false https://github.com/qutebrowser/qutebrowser` and the steps above.

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
@@ -200,6 +200,14 @@ def __init__(self, tab, parent=None):
     def _empty_flags(self):
         return QWebEnginePage.FindFlags(0)  # type: ignore[call-overload]
 
+    def _args_to_flags(self, reverse, ignore_case):
+        flags = self._empty_flags()
+        if self._is_case_sensitive(ignore_case):
+            flags |= QWebEnginePage.FindCaseSensitively
+        if reverse:
+            flags |= QWebEnginePage.FindBackward
+        return flags
+
     def connect_signals(self):
         self._wrap_handler.connect_signal(self._widget.page())
 
@@ -246,17 +254,14 @@ def search(self, text, *, ignore_case=usertypes.IgnoreCase.never,
         # Don't go to next entry on duplicate search
         if self.text == text and self.search_displayed:
             log.webview.debug("Ignoring duplicate search request"
-                              " for {}".format(text))
+                              " for {}, but resetting flags".format(text))
+            self._flags = self._args_to_flags(reverse, ignore_case)
             return
 
         self.text = text
-        self._flags = self._empty_flags()
+        self._flags = self._args_to_flags(reverse, ignore_case)
         self._wrap_handler.reset_match_data()
         self._wrap_handler.flag_wrap = wrap
-        if self._is_case_sensitive(ignore_case):
-            self._flags |= QWebEnginePage.FindCaseSensitively
-        if reverse:
-            self._flags |= QWebEnginePage.FindBackward
 
         self._find(text, self._flags, result_cb, 'search')
 
diff --git a/qutebrowser/browser/webkit/webkittab.py b/qutebrowser/browser/webkit/webkittab.py
--- a/qutebrowser/browser/webkit/webkittab.py
+++ b/qutebrowser/browser/webkit/webkittab.py
@@ -108,6 +108,16 @@ def __init__(self, tab, parent=None):
     def _empty_flags(self):
         return QWebPage.FindFlags(0)  # type: ignore[call-overload]
 
+    def _args_to_flags(self, reverse, ignore_case, wrap):
+        flags = self._empty_flags()
+        if self._is_case_sensitive(ignore_case):
+            flags |= QWebPage.FindCaseSensitively
+        if reverse:
+            flags |= QWebPage.FindBackward
+        if wrap:
+            flags |= QWebPage.FindWrapsAroundDocument
+        return flags
+
     def _call_cb(self, callback, found, text, flags, caller):
         """Call the given callback if it's non-None.
 
@@ -150,7 +160,8 @@ def search(self, text, *, ignore_case=usertypes.IgnoreCase.never,
         # Don't go to next entry on duplicate search
         if self.text == text and self.search_displayed:
             log.webview.debug("Ignoring duplicate search request"
-                              " for {}".format(text))
+                              " for {}, but resetting flags".format(text))
+            self._flags = self._args_to_flags(reverse, ignore_case, wrap)
             return
 
         # Clear old search results, this is done automatically on QtWebEngine.
@@ -158,13 +169,7 @@ def search(self, text, *, ignore_case=usertypes.IgnoreCase.never,
 
         self.text = text
         self.search_displayed = True
-        self._flags = self._empty_flags()
-        if self._is_case_sensitive(ignore_case):
-            self._flags |= QWebPage.FindCaseSensitively
-        if reverse:
-            self._flags |= QWebPage.FindBackward
-        if wrap:
-            self._flags |= QWebPage.FindWrapsAroundDocument
+        self._flags = self._args_to_flags(reverse, ignore_case, wrap)
         # We actually search *twice* - once to highlight everything, then again
         # to get a mark so we can navigate.
         found = self._widget.findText(text, self._flags)


Test patch:
diff --git a/tests/end2end/features/search.feature b/tests/end2end/features/search.feature
--- a/tests/end2end/features/search.feature
+++ b/tests/end2end/features/search.feature
@@ -71,7 +71,25 @@ Feature: Searching on a page
         When I run :search foo
         And I wait for "search found foo" in the log
         And I run :search foo
-        Then "Ignoring duplicate search request for foo" should be logged
+        Then "Ignoring duplicate search request for foo, but resetting flags" should be logged
+
+    Scenario: Reset search direction on duplicate search, forward-to-back
+        When I run :search baz
+        And I wait for "search found baz" in the log
+        And I run :search -r baz
+        And I wait for "Ignoring duplicate search request for baz, but resetting flags" in the log
+        And I run :search-next
+        And I wait for "next_result found baz with flags FindBackward" in the log
+        Then "BAZ" should be found
+
+    Scenario: Reset search direction on duplicate search, back-to-forward
+        When I run :search -r baz
+        And I wait for "search found baz with flags FindBackward" in the log
+        And I run :search baz
+        And I wait for "Ignoring duplicate search request for baz, but resetting flags" in the log
+        And I run :search-next
+        And I wait for "next_result found baz" in the log
+        Then "baz" should be found
 
     ## search.ignore_case