[orca] Web: Prevent structural navigation exiting current modal dialog



commit a46dae117bde3b1ad4aab272be9e1346fcd98cd3
Author: Joanmarie Diggs <jdiggs igalia com>
Date:   Thu Jul 7 13:30:56 2022 +0200

    Web: Prevent structural navigation exiting current modal dialog
    
    When using browse mode inside of a modal dialog, it should not be possible
    to use structural navigation to exit that modal dialog without first
    dismissing it. This commit causes structural navigation objects which are
    outside of the current modal dialog to not be included in the list of
    available objects or reachable via previous/next object.
    
    Fixes issue #254.

 src/orca/script_utilities.py             | 37 ++++++++++++++++++++++++++++++++
 src/orca/scripts/web/script_utilities.py | 14 ++++++++++++
 src/orca/structural_navigation.py        | 19 ++++++++++++++++
 3 files changed, 70 insertions(+)
---
diff --git a/src/orca/script_utilities.py b/src/orca/script_utilities.py
index 40ed63418..bbdf8e54a 100644
--- a/src/orca/script_utilities.py
+++ b/src/orca/script_utilities.py
@@ -1304,6 +1304,43 @@ class Utilities:
 
         return doc
 
+    def isModalDialog(self, obj):
+        if not obj:
+            return False
+
+        try:
+            role = obj.getRole()
+            state = obj.getState()
+        except:
+            msg = "ERROR: Exception getting role and state for %s" % obj
+            debug.println(debug.LEVEL_INFO, msg, True)
+            return False
+
+        return role in [pyatspi.ROLE_DIALOG, pyatspi.ROLE_ALERT] \
+            and state.contains(pyatspi.STATE_MODAL)
+
+    def getModalDialog(self, obj):
+        if not obj:
+            return False
+
+        if self.isModalDialog(obj):
+            return obj
+
+        try:
+            dialog = pyatspi.findAncestor(obj, self.isModalDialog)
+        except:
+            msg = "ERROR: Exception finding ancestor of %s" % obj
+            debug.println(debug.LEVEL_INFO, msg, True)
+            return None
+
+        return dialog
+
+    def isModalDialogDescendant(self, obj):
+        if not obj:
+            return False
+
+        return self.getModalDialog(obj) is not None
+
     def getTable(self, obj):
         if not obj:
             return None
diff --git a/src/orca/scripts/web/script_utilities.py b/src/orca/scripts/web/script_utilities.py
index c2f77f380..e3b867937 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._isEntryDescendant = {}
         self._isGridDescendant = {}
         self._isLabelDescendant = {}
+        self._isModalDialogDescendant = {}
         self._isMenuDescendant = {}
         self._isNavigableToolTipDescendant = {}
         self._isToolBarDescendant = {}
@@ -164,6 +165,7 @@ class Utilities(script_utilities.Utilities):
         self._isGridDescendant = {}
         self._isLabelDescendant = {}
         self._isMenuDescendant = {}
+        self._isModalDialogDescendant = {}
         self._isNavigableToolTipDescendant = {}
         self._isToolBarDescendant = {}
         self._isWebAppDescendant = {}
@@ -3105,6 +3107,18 @@ class Utilities(script_utilities.Utilities):
         self._isMenuDescendant[hash(obj)] = rv
         return rv
 
+    def isModalDialogDescendant(self, obj):
+        if not obj:
+            return False
+
+        rv = self._isModalDialogDescendant.get(hash(obj))
+        if rv is not None:
+            return rv
+
+        rv = super().isModalDialogDescendant(obj)
+        self._isModalDialogDescendant[hash(obj)] = rv
+        return rv
+
     def isNavigableToolTipDescendant(self, obj):
         if not obj:
             return False
diff --git a/src/orca/structural_navigation.py b/src/orca/structural_navigation.py
index f1e36e38d..8957c8b8d 100644
--- a/src/orca/structural_navigation.py
+++ b/src/orca/structural_navigation.py
@@ -673,6 +673,8 @@ class StructuralNavigation:
 
         self._objectCache = {}
 
+        self._inModalDialog = False
+
     def clearCache(self, document=None):
         if document:
             self._objectCache[hash(document)] = {}
@@ -862,6 +864,16 @@ class StructuralNavigation:
         if not structuralNavigationObject.criteria:
             return [], None
 
+        modalDialog = self._script.utilities.getModalDialog(orca_state.locusOfFocus)
+        inModalDialog = bool(modalDialog)
+        if self._inModalDialog != inModalDialog:
+            msg = "STRUCTURAL NAVIGATION: in modal dialog has changed from %s to %s" % \
+                (self._inModalDialog, inModalDialog)
+            debug.println(debug.LEVEL_INFO, msg, True)
+
+            self.clearCache()
+            self._inModalDialog = inModalDialog
+
         document = self._script.utilities.documentFrame()
         cache = self._objectCache.get(hash(document), {})
         key = "%s:%s" % (structuralNavigationObject.objType, arg)
@@ -893,6 +905,13 @@ class StructuralNavigation:
         matches = col.getMatches(rule, col.SORT_ORDER_CANONICAL, 0, True)
         col.freeMatchRule(rule)
 
+        if inModalDialog:
+            originalSize = len(matches)
+            matches = [m for m in matches if pyatspi.findAncestor(m, lambda x: x == modalDialog)]
+            msg = "STRUCTURAL NAVIGATION: Removed %i objects outside of modal dialog %s" % \
+                (originalSize - len(matches), modalDialog)
+            debug.println(debug.LEVEL_INFO, msg, True)
+
         rv = matches.copy(), criteria
         cache[key] = matches, criteria
         self._objectCache[hash(document)] = cache


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