[orca] Add support for aria-roledescription



commit 4a8e54df8b6cb85c3206606af9f08d03c5ca1797
Author: Joanmarie Diggs <jdiggs igalia com>
Date:   Wed Sep 9 21:10:02 2015 -0400

    Add support for aria-roledescription

 src/orca/braille_generator.py                      |    3 +-
 src/orca/formatting.py                             |    8 +-
 src/orca/scripts/web/braille_generator.py          |   14 +++
 src/orca/scripts/web/script_utilities.py           |   16 ++++
 src/orca/scripts/web/speech_generator.py           |   17 ++++
 src/orca/speech_generator.py                       |    3 +-
 test/html/aria-roledescription.html                |   30 ++++++
 .../firefox/aria_roledescription_where_am_i.params |    1 +
 .../firefox/aria_roledescription_where_am_i.py     |   46 ++++++++++
 .../firefox/focus_tracking_roledescriptions.params |    1 +
 .../firefox/focus_tracking_roledescriptions.py     |   96 ++++++++++++++++++++
 .../firefox/line_nav_roledescriptions.params       |    1 +
 .../firefox/line_nav_roledescriptions.py           |   63 +++++++++++++
 13 files changed, 292 insertions(+), 7 deletions(-)
---
diff --git a/src/orca/braille_generator.py b/src/orca/braille_generator.py
index 2015504..f4952d6 100644
--- a/src/orca/braille_generator.py
+++ b/src/orca/braille_generator.py
@@ -156,8 +156,7 @@ class BrailleGenerator(generator.Generator):
             result.append(self.getLocalizedRoleName(obj, role))
         return result
 
-    @staticmethod
-    def getLocalizedRoleName(obj, role=None):
+    def getLocalizedRoleName(self, obj, role=None):
         """Returns the localized name of the given Accessible object; the name
         is suitable to be brailled.
 
diff --git a/src/orca/formatting.py b/src/orca/formatting.py
index a93150d..43b2dce 100644
--- a/src/orca/formatting.py
+++ b/src/orca/formatting.py
@@ -343,8 +343,8 @@ formatting = {
             'basicWhereAmI': 'labelOrName + roleName + value + percentage + ' + MNEMONIC + ' + accelerator + 
required'
             },
         pyatspi.ROLE_SECTION: {
-            'focused': 'labelOrName + readOnly + textRole + currentLineText + allTextSelection',
-            'unfocused': 'labelOrName + readOnly + textRole + currentLineText + allTextSelection + ' + 
MNEMONIC,
+            'focused': 'labelOrName + currentLineText + allTextSelection + roleName',
+            'unfocused': 'labelOrName + currentLineText + allTextSelection + roleName + ' + MNEMONIC,
             },
         pyatspi.ROLE_SLIDER: {
             'focused': 'value',
@@ -635,7 +635,9 @@ formatting = {
             'unfocused': 'asPageTabOrScrollPane'
             },
         pyatspi.ROLE_SECTION: {
-            'unfocused': BRAILLE_TEXT
+            'unfocused': '((substring and ' + BRAILLE_TEXT + ')\
+                          or ([Component(obj, asString(labelAndName + roleName))]\
+                             + (childWidget and ([Region(" ")] + childWidget))))'
             },
         #'REAL_ROLE_SCROLL_PANE': 'default'
         pyatspi.ROLE_SLIDER: {
diff --git a/src/orca/scripts/web/braille_generator.py b/src/orca/scripts/web/braille_generator.py
index ecf4642..690d0a3 100644
--- a/src/orca/scripts/web/braille_generator.py
+++ b/src/orca/scripts/web/braille_generator.py
@@ -41,9 +41,23 @@ class BrailleGenerator(braille_generator.BrailleGenerator):
     def __init__(self, script):
         super().__init__(script)
 
+    def getLocalizedRoleName(self, obj, role=None):
+        if not self._script.utilities.inDocumentContent(obj):
+            return super().getLocalizedRoleName(obj, role)
+
+        roledescription = self._script.utilities.getRoleDescription(obj)
+        if roledescription:
+            return roledescription
+
+        return super().getLocalizedRoleName(obj, role)
+
     def _generateRoleName(self, obj, **args):
         """Prevents some roles from being displayed."""
 
+        roledescription = self._script.utilities.getRoleDescription(obj)
+        if roledescription:
+            return [roledescription]
+
         doNotDisplay = [pyatspi.ROLE_FORM,
                         pyatspi.ROLE_SECTION,
                         pyatspi.ROLE_PARAGRAPH,
diff --git a/src/orca/scripts/web/script_utilities.py b/src/orca/scripts/web/script_utilities.py
index c05eff3..eed1b83 100644
--- a/src/orca/scripts/web/script_utilities.py
+++ b/src/orca/scripts/web/script_utilities.py
@@ -65,6 +65,7 @@ class Utilities(script_utilities.Utilities):
         self._isNonEntryTextWidget = {}
         self._isUselessImage = {}
         self._inferredLabels = {}
+        self._roleDescription = {}
         self._text = {}
         self._tag = {}
         self._treatAsDiv = {}
@@ -103,6 +104,7 @@ class Utilities(script_utilities.Utilities):
         self._isNonEntryTextWidget = {}
         self._isUselessImage = {}
         self._inferredLabels = {}
+        self._roleDescription = {}
         self._tag = {}
         self._treatAsDiv = {}
         self._cleanupContexts()
@@ -319,6 +321,20 @@ class Utilities(script_utilities.Utilities):
 
         return lastChild
 
+    def getRoleDescription(self, obj):
+        rv = self._roleDescription.get(hash(obj))
+        if rv is not None:
+            return rv
+
+        try:
+            attrs = dict([attr.split(':', 1) for attr in obj.getAttributes()])
+        except:
+            attrs = {}
+
+        rv = attrs.get('roledescription', '')
+        self._roleDescription[hash(obj)] = rv
+        return rv
+
     def _getTag(self, obj):
         rv = self._tag.get(hash(obj))
         if rv is not None:
diff --git a/src/orca/scripts/web/speech_generator.py b/src/orca/scripts/web/speech_generator.py
index 6965d42..22446a6 100644
--- a/src/orca/scripts/web/speech_generator.py
+++ b/src/orca/scripts/web/speech_generator.py
@@ -191,12 +191,29 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
     def _generateTextRole(self, obj, **args):
         return self._generateRoleName(obj, **args)
 
+    def getLocalizedRoleName(self, obj, role=None):
+        if not self._script.utilities.inDocumentContent(obj):
+            return super().getLocalizedRoleName(obj, role)
+
+        roledescription = self._script.utilities.getRoleDescription(obj)
+        if roledescription:
+            return roledescription
+
+        return super().getLocalizedRoleName(obj, role)
+
     def _generateRoleName(self, obj, **args):
         if not self._script.utilities.inDocumentContent(obj):
             return super()._generateRoleName(obj, **args)
 
         result = []
         acss = self.voice(speech_generator.SYSTEM)
+
+        roledescription = self._script.utilities.getRoleDescription(obj)
+        if roledescription:
+            result = [roledescription]
+            result.extend(acss)
+            return result
+
         role = args.get('role', obj.getRole())
         force = args.get('force', False)
 
diff --git a/src/orca/speech_generator.py b/src/orca/speech_generator.py
index b74b032..3af6fd3 100644
--- a/src/orca/speech_generator.py
+++ b/src/orca/speech_generator.py
@@ -364,8 +364,7 @@ class SpeechGenerator(generator.Generator):
         """
         return self._generateRoleName(obj, **args)
 
-    @staticmethod
-    def getLocalizedRoleName(obj, role=None):
+    def getLocalizedRoleName(self, obj, role=None):
         """Returns the localized name of the given Accessible object; the name
         is suitable to be spoken.
 
diff --git a/test/html/aria-roledescription.html b/test/html/aria-roledescription.html
new file mode 100644
index 0000000..eb7b948
--- /dev/null
+++ b/test/html/aria-roledescription.html
@@ -0,0 +1,30 @@
+<html>
+<head></head>
+<body>
+<div>Start</div>
+<div tabindex="0">Focus me 1</div>
+<div tabindex="0" aria-roledescription="kill switch">Focus me 2</div>
+<div tabindex="0" role="button">Focus me 3</div>
+<div tabindex="0" role="button" aria-roledescription="kill switch">Focus me 4</div>
+<button>Focus me 5</button>
+<button aria-roledescription="kill switch">Focus me 6</button>
+<div tabindex="0" role="group" aria-label="Presentation" aria-roledescription="slide set">
+  <p>Here are some slides</p>
+  <div tabindex="0" role="region" aria-roledescription="slide" id="slide1" aria-labelledby="slide1heading">
+    <h1 id="slide1heading">First Quarter 2015</h1>
+    <ul>
+        <li>Item 1</li>
+        <li>Item 2</li>
+    </ul>
+  </div>
+  <div tabindex="0" aria-roledescription="slide" id="slide1" aria-labelledby="slide2heading">
+    <h1 id="slide2heading">Second Quarter 2015</h1>
+    <ul>
+        <li>Item 3</li>
+        <li>Item 4</li>
+    </ul>
+  </div>
+</div>
+<div>End</div>
+</body>
+</html>
diff --git a/test/keystrokes/firefox/aria_roledescription_where_am_i.params 
b/test/keystrokes/firefox/aria_roledescription_where_am_i.params
new file mode 100644
index 0000000..8271399
--- /dev/null
+++ b/test/keystrokes/firefox/aria_roledescription_where_am_i.params
@@ -0,0 +1 @@
+PARAMS=$TEST_DIR/../../html/aria-roledescription.html
diff --git a/test/keystrokes/firefox/aria_roledescription_where_am_i.py 
b/test/keystrokes/firefox/aria_roledescription_where_am_i.py
new file mode 100644
index 0000000..d6ec47a
--- /dev/null
+++ b/test/keystrokes/firefox/aria_roledescription_where_am_i.py
@@ -0,0 +1,46 @@
+#!/usr/bin/python
+
+from macaroon.playback import *
+import utils
+
+sequence = MacroSequence()
+
+# Work around some new quirk in Gecko that causes this test to fail if
+# run via the test harness rather than manually.
+sequence.append(KeyComboAction("<Control>r"))
+sequence.append(KeyComboAction("<Control>Home"))
+sequence.append(KeyComboAction("Tab"))
+sequence.append(KeyComboAction("Tab"))
+
+sequence.append(utils.StartRecordingAction())
+sequence.append(KeyComboAction("KP_Enter"))
+sequence.append(utils.AssertPresentationAction(
+    "1. Basic Where Am I on a div with only a roledescription",
+    ["BRAILLE LINE:  'Focus me 2 kill switch'",
+     "     VISIBLE:  'Focus me 2 kill switch', cursor=1",
+     "SPEECH OUTPUT: 'Focus me 2 kill switch'"]))
+
+sequence.append(KeyComboAction("Tab"))
+sequence.append(KeyComboAction("Tab"))
+
+sequence.append(utils.StartRecordingAction())
+sequence.append(KeyComboAction("KP_Enter"))
+sequence.append(utils.AssertPresentationAction(
+    "2. Basic Where Am I on a div with role button and a roledescription",
+    ["BRAILLE LINE:  'Focus me 4 kill switch'",
+     "     VISIBLE:  'Focus me 4 kill switch', cursor=1",
+     "SPEECH OUTPUT: 'Focus me 4 kill switch'"]))
+
+sequence.append(KeyComboAction("Tab"))
+sequence.append(KeyComboAction("Tab"))
+
+sequence.append(utils.StartRecordingAction())
+sequence.append(KeyComboAction("KP_Enter"))
+sequence.append(utils.AssertPresentationAction(
+    "3. Basic Where Am I on a button element with a roledescription",
+    ["BRAILLE LINE:  'Focus me 6 kill switch'",
+     "     VISIBLE:  'Focus me 6 kill switch', cursor=1",
+     "SPEECH OUTPUT: 'Focus me 6 kill switch'"]))
+
+sequence.append(utils.AssertionSummaryAction())
+sequence.start()
diff --git a/test/keystrokes/firefox/focus_tracking_roledescriptions.params 
b/test/keystrokes/firefox/focus_tracking_roledescriptions.params
new file mode 100644
index 0000000..8271399
--- /dev/null
+++ b/test/keystrokes/firefox/focus_tracking_roledescriptions.params
@@ -0,0 +1 @@
+PARAMS=$TEST_DIR/../../html/aria-roledescription.html
diff --git a/test/keystrokes/firefox/focus_tracking_roledescriptions.py 
b/test/keystrokes/firefox/focus_tracking_roledescriptions.py
new file mode 100644
index 0000000..219f979
--- /dev/null
+++ b/test/keystrokes/firefox/focus_tracking_roledescriptions.py
@@ -0,0 +1,96 @@
+#!/usr/bin/python
+
+from macaroon.playback import *
+import utils
+
+sequence = MacroSequence()
+
+sequence.append(KeyComboAction("<Control>Home"))
+
+sequence.append(utils.StartRecordingAction())
+sequence.append(KeyComboAction("Tab"))
+sequence.append(utils.AssertPresentationAction(
+    "1. Tab to a div with no roledescription",
+    ["BRAILLE LINE:  'Focus me 1'",
+     "     VISIBLE:  'Focus me 1', cursor=1",
+     "BRAILLE LINE:  'Focus me 1'",
+     "     VISIBLE:  'Focus me 1', cursor=1",
+     "SPEECH OUTPUT: 'Focus me 1'"]))
+
+sequence.append(utils.StartRecordingAction())
+sequence.append(KeyComboAction("Tab"))
+sequence.append(utils.AssertPresentationAction(
+    "2. Tab to a div with only a roledescription",
+    ["BRAILLE LINE:  'Focus me 2 kill switch'",
+     "     VISIBLE:  'Focus me 2 kill switch', cursor=1",
+     "BRAILLE LINE:  'Focus me 2 kill switch'",
+     "     VISIBLE:  'Focus me 2 kill switch', cursor=1",
+     "SPEECH OUTPUT: 'Focus me 2 kill switch'"]))
+
+sequence.append(utils.StartRecordingAction())
+sequence.append(KeyComboAction("Tab"))
+sequence.append(utils.AssertPresentationAction(
+    "3. Tab to a div with role button",
+    ["BRAILLE LINE:  'Focus me 3 push button'",
+     "     VISIBLE:  'Focus me 3 push button', cursor=1",
+     "SPEECH OUTPUT: 'Focus me 3 push button'"]))
+
+sequence.append(utils.StartRecordingAction())
+sequence.append(KeyComboAction("Tab"))
+sequence.append(utils.AssertPresentationAction(
+    "4. Tab to a div with role button and a roledescription",
+    ["BRAILLE LINE:  'Focus me 4 kill switch'",
+     "     VISIBLE:  'Focus me 4 kill switch', cursor=1",
+     "SPEECH OUTPUT: 'Focus me 4 kill switch'"]))
+
+sequence.append(utils.StartRecordingAction())
+sequence.append(KeyComboAction("Tab"))
+sequence.append(utils.AssertPresentationAction(
+    "5. Tab to a button element",
+    ["BRAILLE LINE:  'Focus me 5 push button'",
+     "     VISIBLE:  'Focus me 5 push button', cursor=1",
+     "SPEECH OUTPUT: 'Focus me 5 push button'"]))
+
+sequence.append(utils.StartRecordingAction())
+sequence.append(KeyComboAction("Tab"))
+sequence.append(utils.AssertPresentationAction(
+    "6. Tab to a button element with a roledescription",
+    ["BRAILLE LINE:  'Focus me 6 kill switch'",
+     "     VISIBLE:  'Focus me 6 kill switch', cursor=1",
+     "SPEECH OUTPUT: 'Focus me 6 kill switch'"]))
+
+sequence.append(utils.StartRecordingAction())
+sequence.append(KeyComboAction("Tab"))
+sequence.append(utils.AssertPresentationAction(
+    "7. Tab to a div with role group, a roledescription, a label, and displayed text",
+    ["BRAILLE LINE:  'Presentation slide set'",
+     "     VISIBLE:  'Presentation slide set', cursor=1",
+     "BRAILLE LINE:  'Here are some slides'",
+     "     VISIBLE:  'Here are some slides', cursor=1",
+     "SPEECH OUTPUT: 'Presentation slide set'",
+     "SPEECH OUTPUT: 'Here are some slides'"]))
+
+sequence.append(utils.StartRecordingAction())
+sequence.append(KeyComboAction("Tab"))
+sequence.append(utils.AssertPresentationAction(
+    "8. Tab to a div with role region, a roledescription, a labelledby, and displayed text",
+    ["BRAILLE LINE:  'First Quarter 2015 slide'",
+     "     VISIBLE:  'First Quarter 2015 slide', cursor=1",
+     "BRAILLE LINE:  'First Quarter 2015 h1'",
+     "     VISIBLE:  'First Quarter 2015 h1', cursor=1",
+     "SPEECH OUTPUT: 'First Quarter 2015 slide'",
+     "SPEECH OUTPUT: 'First Quarter 2015 heading level 1'"]))
+
+sequence.append(utils.StartRecordingAction())
+sequence.append(KeyComboAction("Tab"))
+sequence.append(utils.AssertPresentationAction(
+    "9. Tab to a div with a roledescription, a labelledby, and displayed text",
+    ["BRAILLE LINE:  'Second Quarter 2015 slide'",
+     "     VISIBLE:  'Second Quarter 2015 slide', cursor=1",
+     "BRAILLE LINE:  'Second Quarter 2015 h1'",
+     "     VISIBLE:  'Second Quarter 2015 h1', cursor=1",
+     "SPEECH OUTPUT: 'Second Quarter 2015 slide'",
+     "SPEECH OUTPUT: 'Second Quarter 2015 heading level 1'"]))
+
+sequence.append(utils.AssertionSummaryAction())
+sequence.start()
diff --git a/test/keystrokes/firefox/line_nav_roledescriptions.params 
b/test/keystrokes/firefox/line_nav_roledescriptions.params
new file mode 100644
index 0000000..8271399
--- /dev/null
+++ b/test/keystrokes/firefox/line_nav_roledescriptions.params
@@ -0,0 +1 @@
+PARAMS=$TEST_DIR/../../html/aria-roledescription.html
diff --git a/test/keystrokes/firefox/line_nav_roledescriptions.py 
b/test/keystrokes/firefox/line_nav_roledescriptions.py
new file mode 100644
index 0000000..1f63f0c
--- /dev/null
+++ b/test/keystrokes/firefox/line_nav_roledescriptions.py
@@ -0,0 +1,63 @@
+#!/usr/bin/python
+
+from macaroon.playback import *
+import utils
+
+sequence = MacroSequence()
+
+# Work around some new quirk in Gecko that causes this test to fail if
+# run via the test harness rather than manually.
+sequence.append(KeyComboAction("<Control>r"))
+sequence.append(KeyComboAction("<Control>Home"))
+
+sequence.append(utils.StartRecordingAction())
+sequence.append(KeyComboAction("Down"))
+sequence.append(utils.AssertPresentationAction(
+    "1. Line Down",
+    ["BRAILLE LINE:  'Focus me 1'",
+     "     VISIBLE:  'Focus me 1', cursor=1",
+     "SPEECH OUTPUT: 'Focus me 1'"]))
+
+sequence.append(utils.StartRecordingAction())
+sequence.append(KeyComboAction("Down"))
+sequence.append(utils.AssertPresentationAction(
+    "2. Line Down",
+    ["BRAILLE LINE:  'Focus me 2'",
+     "     VISIBLE:  'Focus me 2', cursor=1",
+     "SPEECH OUTPUT: 'Focus me 2 kill switch'"]))
+
+sequence.append(utils.StartRecordingAction())
+sequence.append(KeyComboAction("Down"))
+sequence.append(utils.AssertPresentationAction(
+    "3. Line Down",
+    ["BRAILLE LINE:  'Focus me 3 push button'",
+     "     VISIBLE:  'Focus me 3 push button', cursor=1",
+     "SPEECH OUTPUT: 'Focus me 3 push button'"]))
+
+sequence.append(utils.StartRecordingAction())
+sequence.append(KeyComboAction("Down"))
+sequence.append(utils.AssertPresentationAction(
+    "4. Line Down",
+    ["BRAILLE LINE:  'Focus me 4 kill switch'",
+     "     VISIBLE:  'Focus me 4 kill switch', cursor=1",
+     "SPEECH OUTPUT: 'Focus me 4 kill switch'"]))
+
+sequence.append(utils.StartRecordingAction())
+sequence.append(KeyComboAction("Down"))
+sequence.append(utils.AssertPresentationAction(
+    "5. Line Down",
+    ["BRAILLE LINE:  'Focus me 5 push button Focus me 6 kill switch'",
+     "     VISIBLE:  'Focus me 5 push button Focus me ', cursor=1",
+     "SPEECH OUTPUT: 'Focus me 5 push button'",
+     "SPEECH OUTPUT: 'Focus me 6 kill switch'"]))
+
+sequence.append(utils.StartRecordingAction())
+sequence.append(KeyComboAction("Down"))
+sequence.append(utils.AssertPresentationAction(
+    "6. Line Down",
+    ["BRAILLE LINE:  'Here are some slides'",
+     "     VISIBLE:  'Here are some slides', cursor=1",
+     "SPEECH OUTPUT: 'Here are some slides'"]))
+
+sequence.append(utils.AssertionSummaryAction())
+sequence.start()


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]