From 3e97c1ce337703dc80d6c0b42a5fb90759d17ca4 Mon Sep 17 00:00:00 2001 From: Storm Dragon Date: Sat, 18 Jul 2026 15:10:43 -0400 Subject: [PATCH] Web fixes. --- src/cthulhu/ax_utilities_event.py | 4 +- src/cthulhu/cthulhuVersion.py | 2 +- src/cthulhu/scripts/web/script.py | 12 ++--- tests/test_web_input_regressions.py | 84 +++++++++++++++++++++++++++-- 4 files changed, 89 insertions(+), 13 deletions(-) diff --git a/src/cthulhu/ax_utilities_event.py b/src/cthulhu/ax_utilities_event.py index 29b0e9c..035aaa1 100644 --- a/src/cthulhu/ax_utilities_event.py +++ b/src/cthulhu/ax_utilities_event.py @@ -254,6 +254,8 @@ class AXUtilitiesEvent: reason = TextEventReason.SELECT_ALL elif mgr.last_event_was_primary_click_or_release(): reason = TextEventReason.MOUSE_PRIMARY_BUTTON + elif mgr.last_event_was_tab_navigation(): + reason = TextEventReason.FOCUS_CHANGE elif AXUtilitiesState.is_editable(obj) or AXUtilitiesRole.is_terminal(obj): if mgr.last_event_was_backspace(): reason = TextEventReason.BACKSPACE @@ -273,8 +275,6 @@ class AXUtilitiesEvent: reason = TextEventReason.UNSPECIFIED_COMMAND elif mgr.last_event_was_printable_key(): reason = TextEventReason.TYPING - elif mgr.last_event_was_tab_navigation(): - reason = TextEventReason.FOCUS_CHANGE elif AXObject.find_ancestor(obj, AXUtilitiesRole.children_are_presentational): reason = TextEventReason.UI_UPDATE return reason diff --git a/src/cthulhu/cthulhuVersion.py b/src/cthulhu/cthulhuVersion.py index 2645702..fc5c7ae 100644 --- a/src/cthulhu/cthulhuVersion.py +++ b/src/cthulhu/cthulhuVersion.py @@ -24,4 +24,4 @@ # Cthulhu project: https://git.stormux.org/storm/cthulhu version = "2026.07.18" -codeName = "master" +codeName = "testing" diff --git a/src/cthulhu/scripts/web/script.py b/src/cthulhu/scripts/web/script.py index c911f03..00be06a 100644 --- a/src/cthulhu/scripts/web/script.py +++ b/src/cthulhu/scripts/web/script.py @@ -60,6 +60,7 @@ from cthulhu.scripts import default from cthulhu.ax_object import AXObject from cthulhu.ax_text import AXText from cthulhu.ax_utilities import AXUtilities +from cthulhu.ax_utilities_event import AXUtilitiesEvent, TextEventReason from .bookmarks import Bookmarks from .braille_generator import BrailleGenerator @@ -2104,6 +2105,7 @@ class Script(default.Script): debug.printMessage(debug.LEVEL_INFO, msg, True) return False + reason = AXUtilitiesEvent.get_text_event_reason(event) obj, offset = self.utilities.getCaretContext(document, False, False) tokens = ["WEB: Context: ", obj, ", ", offset, "(focus: ", cthulhu_state.locusOfFocus, ")"] debug.printTokens(debug.LEVEL_INFO, tokens, True) @@ -2156,14 +2158,10 @@ class Script(default.Script): self.updateBraille(event.source) return True - if self.utilities.lastInputEventWasTab(): - msg = "WEB: Last input event was Tab." + if reason == TextEventReason.FOCUS_CHANGE: + msg = "WEB: Event ignored: Caret moved due to focus change." debug.printMessage(debug.LEVEL_INFO, msg, True) - - if self.utilities.isDocument(event.source): - msg = "WEB: Event ignored: Caret moved in document due to Tab." - debug.printMessage(debug.LEVEL_INFO, msg, True) - return True + return True if self.utilities.inFindContainer(): msg = "WEB: Event handled: Presenting find results" diff --git a/tests/test_web_input_regressions.py b/tests/test_web_input_regressions.py index f29ea20..a966589 100644 --- a/tests/test_web_input_regressions.py +++ b/tests/test_web_input_regressions.py @@ -24,6 +24,7 @@ if soundGeneratorModule is not None and not hasattr(soundGeneratorModule, "Sound soundGeneratorModule.SoundGenerator = _StubSoundGenerator from cthulhu import messages +from cthulhu.ax_utilities_event import AXUtilitiesEvent, TextEventReason from cthulhu.scripts.web import script as web_script from cthulhu.scripts.web import script_utilities as web_script_utilities from cthulhu.scripts.toolkits.Gecko import script_utilities as gecko_script_utilities @@ -336,6 +337,7 @@ class WebFocusSpeechInterruptionRegressionTests(unittest.TestCase): class WebCaretMovedRegressionTests(unittest.TestCase): def _make_script(self): testScript = web_script.Script.__new__(web_script.Script) + testScript._inFocusMode = False testScript._lastCommandWasCaretNav = False testScript._lastCommandWasStructNav = False testScript._lastCommandWasMouseButton = False @@ -351,15 +353,87 @@ class WebCaretMovedRegressionTests(unittest.TestCase): def test_matching_char_nav_caret_event_is_consumed_without_duplicate_presentation(self): testScript = self._make_script() source = "source" - event = mock.Mock(source=source, detail1=4) + event = mock.Mock(type="object:text-caret-moved", source=source, detail1=4) - with mock.patch.object(web_script.cthulhu, "setLocusOfFocus") as setLocusOfFocus: + with ( + mock.patch.object( + AXUtilitiesEvent, + "get_text_event_reason", + return_value=TextEventReason.NAVIGATION_BY_CHARACTER, + ), + mock.patch.object(web_script.cthulhu, "setLocusOfFocus") as setLocusOfFocus, + ): result = web_script.Script.onCaretMoved(testScript, event) self.assertTrue(result) testScript.utilities.setCaretContext.assert_not_called() setLocusOfFocus.assert_not_called() + def test_focus_change_caret_event_does_not_replace_real_focus_event(self): + testScript = self._make_script() + source = object() + event = mock.Mock( + type="object:text-caret-moved", + source=source, + detail1=0, + ) + testScript.utilities.getCaretContext.return_value = ("old-focus", 0) + testScript.utilities.lastInputEventWasCharNav.return_value = False + + with ( + mock.patch.object( + AXUtilitiesEvent, + "get_text_event_reason", + return_value=TextEventReason.FOCUS_CHANGE, + ) as getReason, + mock.patch.object(web_script.cthulhu, "setLocusOfFocus") as setLocusOfFocus, + ): + result = web_script.Script.onCaretMoved(testScript, event) + + self.assertTrue(result) + getReason.assert_called_once_with(event) + testScript.utilities.setCaretContext.assert_not_called() + setLocusOfFocus.assert_not_called() + + +class TextEventReasonRegressionTests(unittest.TestCase): + def test_tab_is_classified_as_focus_change_before_editable_state(self): + oldFocus = object() + source = object() + event = mock.Mock(type="object:text-caret-moved", source=source) + inputManager = mock.Mock() + inputManager.last_event_was_caret_selection.return_value = False + inputManager.last_event_was_caret_navigation.return_value = False + inputManager.last_event_was_select_all.return_value = False + inputManager.last_event_was_primary_click_or_release.return_value = False + inputManager.last_event_was_tab_navigation.return_value = True + + with ( + mock.patch( + "cthulhu.input_event_manager.get_manager", + return_value=inputManager, + ), + mock.patch( + "cthulhu.ax_utilities_event.focus_manager.get_manager" + ) as getFocusManager, + mock.patch( + "cthulhu.ax_utilities_event.AXUtilitiesRole.is_text_input_search", + return_value=False, + ), + mock.patch( + "cthulhu.ax_utilities_event.AXUtilitiesState.is_editable", + return_value=True, + ) as isEditable, + ): + getFocusManager.return_value.get_active_mode_and_object_of_interest.return_value = ( + None, + oldFocus, + ) + reason = AXUtilitiesEvent._get_caret_moved_event_reason(event) + + self.assertEqual(reason, TextEventReason.FOCUS_CHANGE) + isEditable.assert_not_called() + class WebDescriptionChangeRegressionTests(unittest.TestCase): def _make_script(self): @@ -610,7 +684,11 @@ class WebSelectionRegressionTests(unittest.TestCase): testScript = self._make_script() document = object() section = object() - event = mock.Mock(source=section, detail1=-1) + event = mock.Mock( + type="object:text-caret-moved", + source=section, + detail1=-1, + ) manager = mock.Mock() manager.last_event_was_forward_caret_selection.return_value = True manager.last_event_was_backward_caret_selection.return_value = False