Repository: qutebrowser/qutebrowser

Issue:
Switch to tab on notification activation
At least libnotify allows notification senders to request an action to be performed when the user "activates" the notification (e.g. by clicking on it). Browsers typically use this to focus the tab where the notification originated, so users don't have to manually look over their tabs. I would really like for this behaviour to be at least an option if not the default.


Discussion:
Currently the notifications are handled entirely by Qt. This might be possible after #5457 though.
Do you have a better idea if this is possible now that #5457 was merged?
It's still kind of tricky unfortunately, since qutebrowser doesn't know which tab the notification came from. The only information it gets is the origin (i.e. the hostname, not sure if the path is even included) of the notification. It could in theory try to find the tab using that information, but there will be cases where it's wrong (because there are multiple tabs on the same hostname).

Resolving patch:
diff --git a/qutebrowser/browser/webengine/notification.py b/qutebrowser/browser/webengine/notification.py
--- a/qutebrowser/browser/webengine/notification.py
+++ b/qutebrowser/browser/webengine/notification.py
@@ -65,7 +65,7 @@
 
 from qutebrowser.config import config
 from qutebrowser.misc import objects
-from qutebrowser.utils import qtutils, log, utils, debug, message, version
+from qutebrowser.utils import qtutils, log, utils, debug, message, version, objreg
 from qutebrowser.qt import sip
 
 
@@ -368,6 +368,17 @@ def _on_adapter_clicked(self, notification_id: int) -> None:
             # https://www.riverbankcomputing.com/pipermail/pyqt/2020-May/042918.html
             log.misc.debug(f"Ignoring click request for notification {notification_id} "
                            "due to PyQt bug")
+            return
+        self._focus_first_matching_tab(notification)
+
+    def _focus_first_matching_tab(self, notification: "QWebEngineNotification") -> None:
+        for win_id in objreg.window_registry:
+            tabbedbrowser = objreg.get("tabbed-browser", window=win_id, scope="window")
+            for idx, tab in enumerate(tabbedbrowser.widgets()):
+                if tab.url().matches(notification.origin(), QUrl.RemovePath):
+                    tabbedbrowser.widget.setCurrentIndex(idx)
+                    return
+        log.misc.debug(f"No matching tab found for {notification.origin()}")
 
     def _drop_adapter(self) -> None:
         """Drop the currently active adapter (if any).


Test patch:
diff --git a/tests/end2end/features/notifications.feature b/tests/end2end/features/notifications.feature
--- a/tests/end2end/features/notifications.feature
+++ b/tests/end2end/features/notifications.feature
@@ -4,7 +4,8 @@ Feature: Notifications
     HTML5 notification API interaction
 
     Background:
-        Given I open data/javascript/notifications.html
+        Given I clean up open tabs
+        And I open data/javascript/notifications.html
         And I set content.notifications.enabled to true
         And I run :click-element id button
         And I clean up the notification server
@@ -120,8 +121,13 @@ Feature: Notifications
     Scenario: User clicks presented notification
         When I run :click-element id show-button
         And I wait for the javascript message "notification shown"
+        And I open about:blank in a new tab
         And I click the notification
         Then the javascript message "notification clicked" should be logged
+        And the following tabs should be open:
+         - about:blank
+         - data/javascript/notifications.html (active)
+         - about:blank
 
     @pyqtwebengine<5.15.0
     Scenario: User clicks presented notification (old Qt)