[orca] Chromium: Improve presentation of browser pop-up menus



commit 09ee2739aacd44d84bd3b6c8e757db3fb803f947
Author: Joanmarie Diggs <jdiggs igalia com>
Date:   Mon Dec 3 18:38:22 2018 -0500

    Chromium: Improve presentation of browser pop-up menus

 src/orca/scripts/toolkits/Chromium/script.py       | 21 ++++++-
 .../scripts/toolkits/Chromium/script_utilities.py  | 66 +++++++++++++++++++++-
 .../scripts/toolkits/Chromium/speech_generator.py  | 11 ++++
 3 files changed, 95 insertions(+), 3 deletions(-)
---
diff --git a/src/orca/scripts/toolkits/Chromium/script.py b/src/orca/scripts/toolkits/Chromium/script.py
index 202c00a71..370aaa027 100644
--- a/src/orca/scripts/toolkits/Chromium/script.py
+++ b/src/orca/scripts/toolkits/Chromium/script.py
@@ -310,7 +310,8 @@ class Script(web.Script):
             topLevel = self.utilities.topLevelObject(event.source)
             if self.utilities.canBeActiveWindow(topLevel):
                 orca_state.activeWindow = topLevel
-                orca.setLocusOfFocus(event, event.source)
+                notify = not self.utilities.isPopupMenuForCurrentItem(event.source)
+                orca.setLocusOfFocus(event, event.source, notify)
             return
 
         if super().onShowingChanged(event):
@@ -356,6 +357,24 @@ class Script(web.Script):
         if not self.utilities.canBeActiveWindow(event.source):
             return
 
+        if not event.source.name:
+            orca_state.activeWindow = event.source
+
+        # If this is a frame for a popup menu, we don't want to treat
+        # it like a proper window:activate event because it's not as
+        # far as the end-user experience is concerned.
+        activeItem = self.utilities.popupMenuForFrame(event.source)
+        if activeItem:
+            selected = self.utilities.selectedChildren(activeItem)
+            if len(selected) == 1:
+                activeItem = selected[0]
+
+            msg = "CHROMIUM: Setting locusOfFocus to %s" % activeItem
+            orca_state.activeWindow = event.source
+            orca.setLocusOfFocus(event, activeItem)
+            debug.println(debug.LEVEL_INFO, msg, True)
+            return
+
         if super().onWindowActivated(event):
             return
 
diff --git a/src/orca/scripts/toolkits/Chromium/script_utilities.py 
b/src/orca/scripts/toolkits/Chromium/script_utilities.py
index fdc4c5552..7c63f4a2a 100644
--- a/src/orca/scripts/toolkits/Chromium/script_utilities.py
+++ b/src/orca/scripts/toolkits/Chromium/script_utilities.py
@@ -109,8 +109,7 @@ class Utilities(web.Utilities):
             return count
 
         # HACK: Ideally, we'd use the selection interface to get the selected
-        # children and then only present this menu if there isn't a selected
-        # child. But that interface is not implemented yet. This hackaround
+        # child count. But that interface is not implemented yet. This hackaround
         # is extremely non-performant.
         for child in obj:
             if child.getState().contains(pyatspi.STATE_SELECTED):
@@ -120,6 +119,20 @@ class Utilities(web.Utilities):
         debug.println(debug.LEVEL_INFO, msg, True)
         return count
 
+    def selectedChildren(self, obj):
+        result = super().selectedChildren(obj)
+        if result or "Selection" in pyatspi.listInterfaces(obj):
+            return result
+
+        # HACK: Ideally, we'd use the selection interface to get the selected
+        # children. But that interface is not implemented yet. This hackaround
+        # is extremely non-performant.
+        for child in obj:
+            if child.getState().contains(pyatspi.STATE_SELECTED):
+                result.append(child)
+
+        return result
+
     def isMenuWithNoSelectedChild(self, obj):
         if not obj:
             return False
@@ -194,6 +207,55 @@ class Utilities(web.Utilities):
 
         return False
 
+    def isPopupMenuForCurrentItem(self, obj):
+        # When a submenu is closed, it has role menu item. But when that submenu
+        # is opened/expanded, a menu with that same name appears. It would be
+        # nice if there were a connection (parent/child or an accessible relation)
+        # between the two....
+        if not self.treatAsMenu(orca_state.locusOfFocus):
+            return False
+
+        if obj.name and obj.name == orca_state.locusOfFocus.name:
+            return obj.getRole() == pyatspi.ROLE_MENU
+
+        return False
+
+    def isFrameForPopupMenu(self, obj):
+        try:
+            name = obj.name
+            role = obj.getRole()
+            childCount = obj.childCount
+        except:
+            msg = "CHROMIUM: Exception getting properties of %s" % obj
+            debug.println(debug.LEVEL_INFO, msg, True)
+            return False
+
+        # The ancestry of a popup menu appears to be a menu bar (even though
+        # one is not actually showing) contained in a nameless frame. It would
+        # be nice if these things were pruned from the accessibility tree....
+        if name or role != pyatspi.ROLE_FRAME or childCount != 1:
+            return False
+
+        if obj[0].getRole() == pyatspi.ROLE_MENU_BAR:
+            return True
+
+        return False
+
+    def popupMenuForFrame(self, obj):
+        if not self.isFrameForPopupMenu(obj):
+            return None
+
+        try:
+            menu = pyatspi.findDescendant(obj, lambda x: x and x.getRole() == pyatspi.ROLE_MENU)
+        except:
+            msg = "CHROMIUM: Exception finding descendant of %s" % obj
+            debug.println(debug.LEVEL_INFO, msg, True)
+            return None
+
+        msg = "CHROMIUM: HACK: Popup menu for %s: %s" % (obj, menu)
+        debug.println(debug.LEVEL_INFO, msg, True)
+        return menu
+
     def grabFocusWhenSettingCaret(self, obj):
         # HACK: Remove this when setting the caret updates focus.
         msg = "CHROMIUM: HACK: Doing focus grab when setting caret on %s" % obj
diff --git a/src/orca/scripts/toolkits/Chromium/speech_generator.py 
b/src/orca/scripts/toolkits/Chromium/speech_generator.py
index c7b2f9daf..c69284e36 100644
--- a/src/orca/scripts/toolkits/Chromium/speech_generator.py
+++ b/src/orca/scripts/toolkits/Chromium/speech_generator.py
@@ -43,6 +43,17 @@ class SpeechGenerator(web.SpeechGenerator):
     def __init__(self, script):
         super().__init__(script)
 
+    def _generateNewAncestors(self, obj, **args):
+        # Likely a refocused submenu whose functional child was just collapsed.
+        # The new ancestors might technically be new, but they are not as far
+        # as the user is concerned.
+        if self._script.utilities.treatAsMenu(obj):
+            msg = "CHROMIUM: Not generating new ancestors for %s" % obj
+            debug.println(debug.LEVEL_INFO, msg, True)
+            return []
+
+        return super()._generateAncestors(obj, **args)
+
     def generateSpeech(self, obj, **args):
         if self._script.utilities.inDocumentContent(obj):
             return super().generateSpeech(obj, **args)


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