After a fierce battle, Firefox support should be better, not perfect, but better.

This commit is contained in:
Storm Dragon
2026-07-06 21:24:45 -04:00
parent 323d0a3975
commit 278a914630
3 changed files with 168 additions and 9 deletions
+151
View File
@@ -10,6 +10,9 @@ os.environ.setdefault("GSETTINGS_BACKEND", "memory")
gi.require_version("Gdk", "3.0")
gi.require_version("Gtk", "3.0")
gi.require_version("Atspi", "2.0")
from gi.repository import Atspi
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
@@ -23,6 +26,7 @@ if soundGeneratorModule is not None and not hasattr(soundGeneratorModule, "Sound
from cthulhu import messages
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
class WebKeyGrabRegressionTests(unittest.TestCase):
@@ -716,6 +720,124 @@ class WebContentCacheRegressionTests(unittest.TestCase):
utilities._cleanupContexts.assert_called_once_with()
class WebSkipSpaceCaretNavigationRegressionTests(unittest.TestCase):
def _make_utilities(self):
utilities = web_script_utilities.Utilities.__new__(web_script_utilities.Utilities)
utilities.getCaretContext = mock.Mock(return_value=(None, -1))
utilities.treatAsTextObject = mock.Mock(return_value=True)
return utilities
def test_next_context_does_not_skip_named_whole_object_as_space(self):
utilities = self._make_utilities()
start = object()
wholeObject = object()
after = object()
utilities.treatAsTextObject.return_value = False
utilities.findNextCaretInOrder = mock.Mock(
side_effect=[(wholeObject, 0), (after, 0)]
)
with mock.patch.object(
web_script_utilities.AXText,
"get_character_at_offset",
return_value=(" ", 0, 1),
) as getCharacter:
result = web_script_utilities.Utilities.nextContext(
utilities,
start,
0,
skipSpace=True,
)
self.assertEqual(result, (wholeObject, 0))
utilities.treatAsTextObject.assert_called_once_with(wholeObject)
getCharacter.assert_not_called()
utilities.findNextCaretInOrder.assert_called_once_with(start, 0)
def test_previous_context_does_not_skip_named_whole_object_as_space(self):
utilities = self._make_utilities()
start = object()
wholeObject = object()
before = object()
utilities.treatAsTextObject.return_value = False
utilities.findPreviousCaretInOrder = mock.Mock(
side_effect=[(wholeObject, 0), (before, 0)]
)
with mock.patch.object(
web_script_utilities.AXText,
"get_character_at_offset",
return_value=(" ", 0, 1),
) as getCharacter:
result = web_script_utilities.Utilities.previousContext(
utilities,
start,
0,
skipSpace=True,
)
self.assertEqual(result, (wholeObject, 0))
utilities.treatAsTextObject.assert_called_once_with(wholeObject)
getCharacter.assert_not_called()
utilities.findPreviousCaretInOrder.assert_called_once_with(start, 0)
def test_next_context_breaks_skip_space_cycles(self):
utilities = self._make_utilities()
start = object()
first = object()
second = object()
utilities.findNextCaretInOrder = mock.Mock(
side_effect=[(first, 0), (second, 0), (first, 0)]
)
with mock.patch.object(
web_script_utilities.AXText,
"get_character_at_offset",
return_value=(" ", 0, 1),
):
result = web_script_utilities.Utilities.nextContext(
utilities,
start,
0,
skipSpace=True,
)
self.assertEqual(result, (first, 0))
self.assertEqual(utilities.findNextCaretInOrder.call_args_list, [
mock.call(start, 0),
mock.call(first, 0),
mock.call(second, 0),
])
def test_previous_context_breaks_skip_space_cycles(self):
utilities = self._make_utilities()
start = object()
first = object()
second = object()
utilities.findPreviousCaretInOrder = mock.Mock(
side_effect=[(first, 0), (second, 0), (first, 0)]
)
with mock.patch.object(
web_script_utilities.AXText,
"get_character_at_offset",
return_value=(" ", 0, 1),
):
result = web_script_utilities.Utilities.previousContext(
utilities,
start,
0,
skipSpace=True,
)
self.assertEqual(result, (first, 0))
self.assertEqual(utilities.findPreviousCaretInOrder.call_args_list, [
mock.call(start, 0),
mock.call(first, 0),
mock.call(second, 0),
])
class WebSplitLinkLineNavigationRegressionTests(unittest.TestCase):
def _make_utilities(self, link):
utilities = web_script_utilities.Utilities.__new__(web_script_utilities.Utilities)
@@ -867,3 +989,32 @@ class WebSplitLinkLineNavigationRegressionTests(unittest.TestCase):
mock.call(paragraph, 96, layoutMode=False, useCache=False),
],
)
class WebGeckoBrokenTextRecoveryRegressionTests(unittest.TestCase):
def test_gecko_limits_broken_text_recovery_to_sentences(self):
utilities = gecko_script_utilities.Utilities.__new__(gecko_script_utilities.Utilities)
utilities.isContentEditableWithEmbeddedObjects = mock.Mock(return_value=True)
obj = object()
self.assertTrue(
gecko_script_utilities.Utilities._attemptBrokenTextRecovery(
utilities,
obj,
boundary=Atspi.TextBoundaryType.SENTENCE_START,
)
)
self.assertFalse(
gecko_script_utilities.Utilities._attemptBrokenTextRecovery(
utilities,
obj,
boundary=Atspi.TextBoundaryType.LINE_START,
)
)
self.assertFalse(
gecko_script_utilities.Utilities._attemptBrokenTextRecovery(
utilities,
obj,
boundary=Atspi.TextBoundaryType.WORD_START,
)
)