Updated speech handling specifically for web.
This commit is contained in:
@@ -3001,7 +3001,13 @@ class Script(script.Script):
|
|||||||
self.pointOfReference["lastTextUnitSpoken"] = "word"
|
self.pointOfReference["lastTextUnitSpoken"] = "word"
|
||||||
|
|
||||||
def presentObject(self, obj, **args):
|
def presentObject(self, obj, **args):
|
||||||
interrupt = args.get("interrupt", False)
|
has_interrupt_request = "interrupt" in args
|
||||||
|
interrupt = self.should_interrupt_for_object_presentation(
|
||||||
|
args.get("interrupt", False),
|
||||||
|
args.pop("interrupt_already_handled", False),
|
||||||
|
)
|
||||||
|
if has_interrupt_request:
|
||||||
|
args["interrupt"] = interrupt
|
||||||
tokens = ["DEFAULT: Presenting object", obj, ". Interrupt:", interrupt]
|
tokens = ["DEFAULT: Presenting object", obj, ". Interrupt:", interrupt]
|
||||||
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
||||||
|
|
||||||
@@ -3421,6 +3427,20 @@ class Script(script.Script):
|
|||||||
if self.utilities.shouldInterruptForLocusOfFocusChange(oldFocus, newFocus, event):
|
if self.utilities.shouldInterruptForLocusOfFocusChange(oldFocus, newFocus, event):
|
||||||
self.presentationInterrupt()
|
self.presentationInterrupt()
|
||||||
|
|
||||||
|
def should_interrupt_for_object_presentation(
|
||||||
|
self,
|
||||||
|
requested: bool,
|
||||||
|
already_handled: bool = False,
|
||||||
|
) -> bool:
|
||||||
|
"""Returns whether object presentation should interrupt prior output."""
|
||||||
|
|
||||||
|
if requested and already_handled:
|
||||||
|
msg = "DEFAULT: Object presentation interruption already handled by input dispatch"
|
||||||
|
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||||
|
return False
|
||||||
|
|
||||||
|
return requested
|
||||||
|
|
||||||
def presentKeyboardEvent(self, event):
|
def presentKeyboardEvent(self, event):
|
||||||
"""Convenience method to present the KeyboardEvent event. Returns True
|
"""Convenience method to present the KeyboardEvent event. Returns True
|
||||||
if we fully present the event; False otherwise."""
|
if we fully present the event; False otherwise."""
|
||||||
|
|||||||
@@ -1183,7 +1183,13 @@ class Script(default.Script):
|
|||||||
super().presentObject(obj, **args)
|
super().presentObject(obj, **args)
|
||||||
return
|
return
|
||||||
|
|
||||||
interrupt = args.get("interrupt", False)
|
has_interrupt_request = "interrupt" in args
|
||||||
|
interrupt = self.should_interrupt_for_object_presentation(
|
||||||
|
args.get("interrupt", False),
|
||||||
|
args.pop("interrupt_already_handled", False),
|
||||||
|
)
|
||||||
|
if has_interrupt_request:
|
||||||
|
args["interrupt"] = interrupt
|
||||||
tokens = ["WEB: Presenting object", obj, ". Interrupt:", interrupt]
|
tokens = ["WEB: Presenting object", obj, ". Interrupt:", interrupt]
|
||||||
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
||||||
|
|
||||||
|
|||||||
@@ -1221,7 +1221,13 @@ class StructuralNavigation:
|
|||||||
return
|
return
|
||||||
|
|
||||||
AXEventSynthesizer.scroll_to_top_edge(obj)
|
AXEventSynthesizer.scroll_to_top_edge(obj)
|
||||||
self._script.presentObject(obj, offset=offset, priorObj=priorObj, interrupt=True)
|
self._script.presentObject(
|
||||||
|
obj,
|
||||||
|
offset=offset,
|
||||||
|
priorObj=priorObj,
|
||||||
|
interrupt=True,
|
||||||
|
interrupt_already_handled=True,
|
||||||
|
)
|
||||||
|
|
||||||
def _presentWithSayAll(self, obj, offset):
|
def _presentWithSayAll(self, obj, offset):
|
||||||
if self._script.inSayAll() \
|
if self._script.inSayAll() \
|
||||||
|
|||||||
@@ -17,6 +17,25 @@ from cthulhu import structural_navigation
|
|||||||
|
|
||||||
|
|
||||||
class StructuralNavigationTableRegressionTests(unittest.TestCase):
|
class StructuralNavigationTableRegressionTests(unittest.TestCase):
|
||||||
|
def test_object_presentation_marks_keyboard_interruption_as_already_handled(self):
|
||||||
|
navigator = structural_navigation.StructuralNavigation.__new__(
|
||||||
|
structural_navigation.StructuralNavigation
|
||||||
|
)
|
||||||
|
navigator._script = mock.Mock()
|
||||||
|
navigator._presentWithSayAll = mock.Mock(return_value=False)
|
||||||
|
target = object()
|
||||||
|
|
||||||
|
with mock.patch.object(structural_navigation.AXEventSynthesizer, "scroll_to_top_edge"):
|
||||||
|
navigator._presentObject(target, 3, "prior")
|
||||||
|
|
||||||
|
navigator._script.presentObject.assert_called_once_with(
|
||||||
|
target,
|
||||||
|
offset=3,
|
||||||
|
priorObj="prior",
|
||||||
|
interrupt=True,
|
||||||
|
interrupt_already_handled=True,
|
||||||
|
)
|
||||||
|
|
||||||
def test_table_cell_coordinates_do_not_interrupt_cell_contents(self):
|
def test_table_cell_coordinates_do_not_interrupt_cell_contents(self):
|
||||||
navigator = structural_navigation.StructuralNavigation.__new__(
|
navigator = structural_navigation.StructuralNavigation.__new__(
|
||||||
structural_navigation.StructuralNavigation
|
structural_navigation.StructuralNavigation
|
||||||
|
|||||||
@@ -278,6 +278,78 @@ class WebPresentationModeSpeechRegressionTests(unittest.TestCase):
|
|||||||
testScript.presentMessage.assert_called_once_with(messages.MODE_FOCUS, interrupt=True)
|
testScript.presentMessage.assert_called_once_with(messages.MODE_FOCUS, interrupt=True)
|
||||||
|
|
||||||
|
|
||||||
|
class WebObjectPresentationInterruptionRegressionTests(unittest.TestCase):
|
||||||
|
@staticmethod
|
||||||
|
def _make_script():
|
||||||
|
testScript = web_script.Script.__new__(web_script.Script)
|
||||||
|
testScript._lastCommandWasCaretNav = False
|
||||||
|
testScript._loadingDocumentContent = False
|
||||||
|
testScript.utilities = mock.Mock()
|
||||||
|
testScript.utilities.inDocumentContent.return_value = True
|
||||||
|
testScript.utilities.getTable.return_value = None
|
||||||
|
testScript.utilities.get_objectContentsAtOffset.return_value = ["target"]
|
||||||
|
testScript.displayContents = mock.Mock()
|
||||||
|
testScript.speakContents = mock.Mock()
|
||||||
|
return testScript
|
||||||
|
|
||||||
|
def test_keyboard_owned_object_presentation_does_not_interrupt_again(self):
|
||||||
|
testScript = self._make_script()
|
||||||
|
target = object()
|
||||||
|
|
||||||
|
with (
|
||||||
|
mock.patch.object(web_script.AXUtilities, "is_document", return_value=False),
|
||||||
|
mock.patch.object(web_script.AXUtilities, "is_status_bar", return_value=False),
|
||||||
|
mock.patch.object(web_script.AXUtilities, "is_entry", return_value=False),
|
||||||
|
):
|
||||||
|
web_script.Script.presentObject(
|
||||||
|
testScript,
|
||||||
|
target,
|
||||||
|
interrupt=True,
|
||||||
|
interrupt_already_handled=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
testScript.speakContents.assert_called_once_with(["target"], interrupt=False)
|
||||||
|
|
||||||
|
def test_non_keyboard_object_presentation_still_interrupts(self):
|
||||||
|
testScript = self._make_script()
|
||||||
|
target = object()
|
||||||
|
|
||||||
|
with (
|
||||||
|
mock.patch.object(web_script.AXUtilities, "is_document", return_value=False),
|
||||||
|
mock.patch.object(web_script.AXUtilities, "is_status_bar", return_value=False),
|
||||||
|
mock.patch.object(web_script.AXUtilities, "is_entry", return_value=False),
|
||||||
|
):
|
||||||
|
web_script.Script.presentObject(testScript, target, interrupt=True)
|
||||||
|
|
||||||
|
testScript.speakContents.assert_called_once_with(["target"], interrupt=True)
|
||||||
|
|
||||||
|
def test_web_alert_keeps_non_keyboard_interrupt_request(self):
|
||||||
|
testScript = self._make_script()
|
||||||
|
testScript._lastCommandWasStructNav = False
|
||||||
|
testScript._lastMouseButtonContext = (None, -1)
|
||||||
|
testScript.lastMouseRoutingTime = 0
|
||||||
|
testScript.utilities.eventIsBrowserUINoise.return_value = False
|
||||||
|
testScript.utilities.isLiveRegion.return_value = False
|
||||||
|
testScript.utilities.getTopLevelDocumentForObject.return_value = "document"
|
||||||
|
testScript.utilities.isZombie.return_value = False
|
||||||
|
testScript.utilities.handleEventFromContextReplicant.return_value = False
|
||||||
|
testScript.utilities.lastQueuedLiveRegion.return_value = None
|
||||||
|
testScript.presentObject = mock.Mock()
|
||||||
|
alert = object()
|
||||||
|
event = mock.Mock(source=object(), any_data=alert)
|
||||||
|
|
||||||
|
with (
|
||||||
|
mock.patch.object(web_script.AXObject, "clear_cache_now"),
|
||||||
|
mock.patch.object(web_script.AXUtilities, "is_busy", return_value=False),
|
||||||
|
mock.patch.object(web_script.AXUtilities, "is_alert", return_value=True),
|
||||||
|
mock.patch.object(web_script.AXUtilities, "get_focused_object", return_value=None),
|
||||||
|
):
|
||||||
|
result = web_script.Script.onChildrenAdded(testScript, event)
|
||||||
|
|
||||||
|
self.assertTrue(result)
|
||||||
|
testScript.presentObject.assert_called_once_with(alert, interrupt=True)
|
||||||
|
|
||||||
|
|
||||||
class WebFocusSpeechInterruptionRegressionTests(unittest.TestCase):
|
class WebFocusSpeechInterruptionRegressionTests(unittest.TestCase):
|
||||||
def _make_script(self):
|
def _make_script(self):
|
||||||
testScript = web_script.Script.__new__(web_script.Script)
|
testScript = web_script.Script.__new__(web_script.Script)
|
||||||
|
|||||||
Reference in New Issue
Block a user