Backported Chrome omnibox fix from Orca.
This commit is contained in:
@@ -148,14 +148,42 @@ class AXObject:
|
||||
if not toolkit_name.startswith("qt"):
|
||||
return False
|
||||
|
||||
if not AXObject._can_reach_application(obj):
|
||||
tokens = ["AXObject:", obj, "has broken ancestry. See qt bug 130116."]
|
||||
debug.print_tokens(debug.LEVEL_INFO, tokens, True)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def _can_reach_application(obj: Atspi.Accessible) -> bool:
|
||||
"""Returns True if obj's ancestry reaches the application."""
|
||||
|
||||
reached_app = False
|
||||
parent = AXObject.get_parent(obj)
|
||||
while parent and not reached_app:
|
||||
reached_app = AXObject.get_role(parent) == Atspi.Role.APPLICATION
|
||||
parent = AXObject.get_parent(parent)
|
||||
|
||||
if not reached_app:
|
||||
tokens = ["AXObject:", obj, "has broken ancestry. See qt bug 130116."]
|
||||
return reached_app
|
||||
|
||||
@staticmethod
|
||||
def has_broken_popup_ancestry(obj: Atspi.Accessible) -> bool:
|
||||
"""Returns True if obj is a popup item whose ancestry is broken."""
|
||||
|
||||
if obj is None or AXObject.is_dead(obj):
|
||||
return False
|
||||
|
||||
# Chromium Omnibox popups can lose their path back to the frame after
|
||||
# the popup is closed and reopened.
|
||||
if not AXObject.get_toolkit_name(obj).startswith("chromium"):
|
||||
return False
|
||||
|
||||
if AXObject.get_role(obj) != Atspi.Role.LIST_ITEM:
|
||||
return False
|
||||
|
||||
if not AXObject._can_reach_application(obj):
|
||||
tokens = ["AXObject:", obj, "has broken popup ancestry."]
|
||||
debug.print_tokens(debug.LEVEL_INFO, tokens, True)
|
||||
return True
|
||||
|
||||
|
||||
@@ -348,6 +348,13 @@ class FocusManager:
|
||||
tokens.extend(["in", app])
|
||||
_log_tokens(tokens)
|
||||
|
||||
if frame is None and AXObject.has_broken_popup_ancestry(self._focus):
|
||||
_log_tokens(
|
||||
["Not clearing active window; focus", self._focus, "is in popup with broken ancestry"],
|
||||
"broken-popup-ancestry",
|
||||
)
|
||||
return
|
||||
|
||||
if frame == self._window:
|
||||
_log("Setting active window to existing active window", "no-change")
|
||||
elif frame is None:
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
import gi
|
||||
gi.require_version("Atspi", "2.0")
|
||||
from gi.repository import Atspi
|
||||
|
||||
from cthulhu import ax_object
|
||||
from cthulhu import cthulhu_state
|
||||
from cthulhu import focus_manager
|
||||
|
||||
|
||||
class ChromiumOmniboxRegressionTests(unittest.TestCase):
|
||||
def tearDown(self):
|
||||
cthulhu_state.activeWindow = None
|
||||
cthulhu_state.locusOfFocus = None
|
||||
|
||||
def test_detects_chromium_popup_item_with_broken_ancestry(self):
|
||||
popupItem = object()
|
||||
|
||||
with (
|
||||
mock.patch.object(ax_object.AXObject, "is_dead", return_value=False),
|
||||
mock.patch.object(ax_object.AXObject, "get_toolkit_name", return_value="chromium"),
|
||||
mock.patch.object(ax_object.AXObject, "get_role", return_value=Atspi.Role.LIST_ITEM),
|
||||
mock.patch.object(ax_object.AXObject, "get_parent", return_value=None),
|
||||
mock.patch.object(ax_object.debug, "print_tokens"),
|
||||
):
|
||||
self.assertTrue(ax_object.AXObject.has_broken_popup_ancestry(popupItem))
|
||||
|
||||
def test_ignores_chromium_popup_item_with_valid_ancestry(self):
|
||||
popupItem = object()
|
||||
parent = object()
|
||||
|
||||
def get_role(obj):
|
||||
if obj is popupItem:
|
||||
return Atspi.Role.LIST_ITEM
|
||||
return Atspi.Role.APPLICATION
|
||||
|
||||
with (
|
||||
mock.patch.object(ax_object.AXObject, "is_dead", return_value=False),
|
||||
mock.patch.object(ax_object.AXObject, "get_toolkit_name", return_value="chromium"),
|
||||
mock.patch.object(ax_object.AXObject, "get_role", side_effect=get_role),
|
||||
mock.patch.object(ax_object.AXObject, "get_parent", side_effect=[parent, None]),
|
||||
):
|
||||
self.assertFalse(ax_object.AXObject.has_broken_popup_ancestry(popupItem))
|
||||
|
||||
def test_preserves_active_window_when_chromium_popup_ancestry_is_broken(self):
|
||||
activeWindow = object()
|
||||
popupItem = object()
|
||||
cthulhu_state.activeWindow = activeWindow
|
||||
cthulhu_state.locusOfFocus = popupItem
|
||||
|
||||
controller = mock.Mock()
|
||||
app = mock.Mock()
|
||||
manager = None
|
||||
with (
|
||||
mock.patch.object(focus_manager.dbus_service, "get_remote_controller", return_value=controller),
|
||||
mock.patch.object(focus_manager.AXObject, "has_broken_popup_ancestry", return_value=True),
|
||||
):
|
||||
manager = focus_manager.FocusManager(app)
|
||||
manager.set_active_window(None)
|
||||
|
||||
self.assertIs(manager.get_active_window(), activeWindow)
|
||||
self.assertIs(cthulhu_state.activeWindow, activeWindow)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user