[orca] Support navigation within focusable tooltips in web applications
- From: Joanmarie Diggs <joanied src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [orca] Support navigation within focusable tooltips in web applications
- Date: Fri, 17 Apr 2020 22:13:30 +0000 (UTC)
commit fd16615c4cacbb4ee389bd37457978af072c0fd3
Author: Joanmarie Diggs <jdiggs igalia com>
Date: Fri Apr 17 15:31:55 2020 -0400
Support navigation within focusable tooltips in web applications
src/orca/formatting.py | 5 +++--
src/orca/messages.py | 4 ++++
src/orca/scripts/web/script.py | 28 ++++++++++++++++++------
src/orca/scripts/web/script_utilities.py | 37 ++++++++++++++++++++++++++++----
src/orca/scripts/web/speech_generator.py | 3 ++-
src/orca/speech_generator.py | 19 +++++++++++-----
6 files changed, 78 insertions(+), 18 deletions(-)
---
diff --git a/src/orca/formatting.py b/src/orca/formatting.py
index 9512e35cc..38ea48975 100644
--- a/src/orca/formatting.py
+++ b/src/orca/formatting.py
@@ -508,8 +508,9 @@ formatting = {
'unfocused': 'labelAndName + roleName',
},
pyatspi.ROLE_TOOL_TIP: {
- 'unfocused': 'labelAndName',
- 'basicWhereAmI': 'labelAndName'
+ 'focused': 'leaving or roleName',
+ 'unfocused': 'roleName + labelAndName',
+ 'basicWhereAmI': 'roleName + labelAndName'
},
pyatspi.ROLE_TREE_ITEM: {
'focused': 'expandableState',
diff --git a/src/orca/messages.py b/src/orca/messages.py
index 58199759a..e314999d9 100644
--- a/src/orca/messages.py
+++ b/src/orca/messages.py
@@ -1231,6 +1231,10 @@ LEAVING_PANEL = _("leaving panel.")
# table and then navigates out of it.
LEAVING_TABLE = _("leaving table.")
+# Translators: This message is presented when a user is navigating within a
+# tooltip in a web application and then navigates out of it.
+LEAVING_TOOL_TIP = _("leaving tooltip.")
+
# Translators: This message is presented when a user is navigating within
# a document container and then navigates out of it. The word or phrase
# that follows "leaving" should be consistent with the translation provided
diff --git a/src/orca/scripts/web/script.py b/src/orca/scripts/web/script.py
index 6fcb906cf..8866a3c51 100644
--- a/src/orca/scripts/web/script.py
+++ b/src/orca/scripts/web/script.py
@@ -778,7 +778,7 @@ class Script(default.Script):
return self._browseModeIsSticky
- def useFocusMode(self, obj):
+ def useFocusMode(self, obj, prevObj=None):
"""Returns True if we should use focus mode in obj."""
if self._focusModeIsSticky:
@@ -798,7 +798,8 @@ class Script(default.Script):
return False
if not _settingsManager.getSetting('caretNavTriggersFocusMode') \
- and self._lastCommandWasCaretNav:
+ and self._lastCommandWasCaretNav \
+ and not self.utilities.isNavigableToolTipDescendant(prevObj):
msg = "WEB: Not using focus mode due to caret nav settings"
debug.println(debug.LEVEL_INFO, msg, True)
return False
@@ -816,6 +817,11 @@ class Script(default.Script):
return True
if self._inFocusMode and self.utilities.isWebAppDescendant(obj):
+ if self.utilities.forceBrowseModeForWebAppDescendant(obj):
+ msg = "WEB: Forcing browse mode for web app descendant %s" % obj
+ debug.println(debug.LEVEL_INFO, msg, True)
+ return False
+
msg = "WEB: Staying in focus mode because we're inside a web application"
debug.println(debug.LEVEL_INFO, msg, True)
return True
@@ -1275,7 +1281,7 @@ class Script(default.Script):
if not self._focusModeIsSticky \
and not self._browseModeIsSticky \
- and self.useFocusMode(newFocus) != self._inFocusMode:
+ and self.useFocusMode(newFocus, oldFocus) != self._inFocusMode:
self.togglePresentationMode(None)
return True
@@ -1840,10 +1846,16 @@ class Script(default.Script):
debug.println(debug.LEVEL_INFO, msg, True)
return False
+ role = event.source.getRole()
if self.utilities.isWebAppDescendant(event.source):
if self._browseModeIsSticky:
msg = "WEB: Web app descendant claimed focus, but browse mode is sticky"
debug.println(debug.LEVEL_INFO, msg, True)
+ elif role == pyatspi.ROLE_TOOL_TIP \
+ and pyatspi.findAncestor(orca_state.locusOfFocus, lambda x: x and x == event.source):
+ msg = "WEB: Event believed to be side effect of tooltip navigation."
+ debug.println(debug.LEVEL_INFO, msg, True)
+ return True
else:
msg = "WEB: Event handled: Setting locusOfFocus to web app descendant"
debug.println(debug.LEVEL_INFO, msg, True)
@@ -1856,7 +1868,6 @@ class Script(default.Script):
debug.println(debug.LEVEL_INFO, msg, True)
return False
- role = event.source.getRole()
if role in [pyatspi.ROLE_DIALOG, pyatspi.ROLE_ALERT]:
msg = "WEB: Event handled: Setting locusOfFocus to event source"
debug.println(debug.LEVEL_INFO, msg, True)
@@ -2021,9 +2032,14 @@ class Script(default.Script):
return True
if self.utilities.isWebAppDescendant(event.source):
- msg = "WEB: Event source is web app descendant"
+ if self._inFocusMode:
+ msg = "WEB: Event source is web app descendant and we're in focus mode"
+ debug.println(debug.LEVEL_INFO, msg, True)
+ return False
+
+ msg = "WEB: Event source is web app descendant and we're in browse mode"
debug.println(debug.LEVEL_INFO, msg, True)
- return False
+ return True
obj, offset = self.utilities.getCaretContext()
ancestor = self.utilities.commonAncestor(obj, event.source)
diff --git a/src/orca/scripts/web/script_utilities.py b/src/orca/scripts/web/script_utilities.py
index 0b839b7f3..4d8b3d06c 100644
--- a/src/orca/scripts/web/script_utilities.py
+++ b/src/orca/scripts/web/script_utilities.py
@@ -66,6 +66,7 @@ class Utilities(script_utilities.Utilities):
self._isGridDescendant = {}
self._isLabelDescendant = {}
self._isMenuDescendant = {}
+ self._isNavigableToolTipDescendant = {}
self._isToolBarDescendant = {}
self._isWebAppDescendant = {}
self._isLayoutOnly = {}
@@ -139,6 +140,7 @@ class Utilities(script_utilities.Utilities):
self._isGridDescendant = {}
self._isLabelDescendant = {}
self._isMenuDescendant = {}
+ self._isNavigableToolTipDescendant = {}
self._isToolBarDescendant = {}
self._isWebAppDescendant = {}
self._isLayoutOnly = {}
@@ -393,7 +395,8 @@ class Utilities(script_utilities.Utilities):
if self._script.focusModeIsSticky():
return
- self.clearTextSelection(orca_state.locusOfFocus)
+ oldFocus = orca_state.locusOfFocus
+ self.clearTextSelection(oldFocus)
orca.setLocusOfFocus(None, obj, notifyScript=False)
if grabFocus:
self.grabFocus(obj)
@@ -409,7 +412,7 @@ class Utilities(script_utilities.Utilities):
msg = "WEB: Caret set to %i in %s" % (offset, obj)
debug.println(debug.LEVEL_INFO, msg, True)
- if self._script.useFocusMode(obj) != self._script.inFocusMode():
+ if self._script.useFocusMode(obj, oldFocus) != self._script.inFocusMode():
self._script.togglePresentationMode(None)
if obj:
@@ -1007,7 +1010,6 @@ class Utilities(script_utilities.Utilities):
pyatspi.ROLE_PUSH_BUTTON,
pyatspi.ROLE_TOGGLE_BUTTON,
pyatspi.ROLE_TOOL_BAR,
- pyatspi.ROLE_TOOL_TIP,
pyatspi.ROLE_TREE,
pyatspi.ROLE_TREE_ITEM,
pyatspi.ROLE_TREE_TABLE]
@@ -1874,6 +1876,15 @@ class Utilities(script_utilities.Utilities):
return False
+ def forceBrowseModeForWebAppDescendant(self, obj):
+ if not self.isWebAppDescendant(obj):
+ return False
+
+ if obj.getRole() == pyatspi.ROLE_TOOL_TIP:
+ return obj.getState().contains(pyatspi.STATE_FOCUSED)
+
+ return False
+
def isFocusModeWidget(self, obj):
try:
role = obj.getRole()
@@ -2774,6 +2785,23 @@ class Utilities(script_utilities.Utilities):
self._isMenuDescendant[hash(obj)] = rv
return rv
+ def isNavigableToolTipDescendant(self, obj):
+ if not obj:
+ return False
+
+ rv = self._isNavigableToolTipDescendant.get(hash(obj))
+ if rv is not None:
+ return rv
+
+ isToolTip = lambda x: x and x.getRole() == pyatspi.ROLE_TOOL_TIP
+ if isToolTip(obj):
+ ancestor = obj
+ else:
+ ancestor = pyatspi.findAncestor(obj, isToolTip)
+ rv = ancestor and not self.isNonNavigablePopup(ancestor)
+ self._isNavigableToolTipDescendant[hash(obj)] = rv
+ return rv
+
def isToolBarDescendant(self, obj):
if not obj:
return False
@@ -3601,7 +3629,8 @@ class Utilities(script_utilities.Utilities):
if rv is not None:
return rv
- rv = obj.getRole() == pyatspi.ROLE_TOOL_TIP
+ rv = obj.getRole() == pyatspi.ROLE_TOOL_TIP \
+ and not obj.getState().contains(pyatspi.STATE_FOCUSABLE)
self._isNonNavigablePopup[hash(obj)] = rv
return rv
diff --git a/src/orca/scripts/web/speech_generator.py b/src/orca/scripts/web/speech_generator.py
index eaf824d84..ab91d7c84 100644
--- a/src/orca/scripts/web/speech_generator.py
+++ b/src/orca/scripts/web/speech_generator.py
@@ -83,7 +83,8 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
if self._script.utilities.isLink(obj) \
or self._script.utilities.isLandmark(obj) \
- or self._script.utilities.isMath(obj):
+ or self._script.utilities.isMath(obj) \
+ or obj.getRole() == pyatspi.ROLE_TOOL_TIP:
return result
args['stopAtRoles'] = [pyatspi.ROLE_DOCUMENT_FRAME,
diff --git a/src/orca/speech_generator.py b/src/orca/speech_generator.py
index a2bb77c29..36eeaacbb 100644
--- a/src/orca/speech_generator.py
+++ b/src/orca/speech_generator.py
@@ -1756,13 +1756,14 @@ class SpeechGenerator(generator.Generator):
'ROLE_CONTENT_INSERTION',
'ROLE_CONTENT_MARK',
'ROLE_CONTENT_SUGGESTION',
- pyatspi.ROLE_FORM,
- pyatspi.ROLE_LANDMARK,
'ROLE_DPUB_LANDMARK',
'ROLE_DPUB_SECTION',
+ pyatspi.ROLE_FORM,
+ pyatspi.ROLE_LANDMARK,
pyatspi.ROLE_LIST,
pyatspi.ROLE_PANEL,
- pyatspi.ROLE_TABLE]
+ pyatspi.ROLE_TABLE,
+ pyatspi.ROLE_TOOL_TIP]
enabled, disabled = [], []
if self._script.inSayAll():
@@ -1774,6 +1775,7 @@ class SpeechGenerator(generator.Generator):
enabled.append(pyatspi.ROLE_LIST)
if _settingsManager.getSetting('sayAllContextPanel'):
enabled.extend([pyatspi.ROLE_PANEL,
+ pyatspi.ROLE_TOOL_TIP,
'ROLE_CONTENT_DELETION',
'ROLE_CONTENT_INSERTION',
'ROLE_CONTENT_MARK',
@@ -1792,6 +1794,7 @@ class SpeechGenerator(generator.Generator):
enabled.append(pyatspi.ROLE_LIST)
if _settingsManager.getSetting('speakContextPanel'):
enabled.extend([pyatspi.ROLE_PANEL,
+ pyatspi.ROLE_TOOL_TIP,
'ROLE_CONTENT_DELETION',
'ROLE_CONTENT_INSERTION',
'ROLE_CONTENT_MARK',
@@ -1917,6 +1920,8 @@ class SpeechGenerator(generator.Generator):
result = ['']
elif role == pyatspi.ROLE_FORM:
result.append(messages.LEAVING_FORM)
+ elif role == pyatspi.ROLE_TOOL_TIP:
+ result.append(messages.LEAVING_TOOL_TIP)
elif role == 'ROLE_CONTENT_DELETION':
result.append(messages.CONTENT_DELETION_END)
elif role == 'ROLE_CONTENT_INSERTION':
@@ -1979,6 +1984,9 @@ class SpeechGenerator(generator.Generator):
stopAtRoles = args.get('stopAtRoles', [])
stopAtRoles.extend([pyatspi.ROLE_APPLICATION, pyatspi.ROLE_MENU_BAR])
+ stopAfterRoles = args.get('stopAfterRoles', [])
+ stopAfterRoles.extend([pyatspi.ROLE_TOOL_TIP])
+
presentOnce = [pyatspi.ROLE_BLOCK_QUOTE, pyatspi.ROLE_LIST]
presentCommonAncestor = False
@@ -2010,7 +2018,7 @@ class SpeechGenerator(generator.Generator):
ancestors.append(parent)
ancestorRoles.append(parentRole)
- if parent == commonAncestor:
+ if parent == commonAncestor or parentRole in stopAfterRoles:
break
parent = parent.parent
@@ -2069,7 +2077,8 @@ class SpeechGenerator(generator.Generator):
'ROLE_DPUB_SECTION',
pyatspi.ROLE_LIST,
pyatspi.ROLE_PANEL,
- pyatspi.ROLE_TABLE]
+ pyatspi.ROLE_TABLE,
+ pyatspi.ROLE_TOOL_TIP]
result = []
if self._script.utilities.isBlockquote(priorObj):
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]