Fixed crash bug with landmark navigation.

This commit is contained in:
Storm Dragon
2025-12-28 19:48:47 -05:00
parent 85b358d22b
commit fee5800220
2 changed files with 102 additions and 2 deletions
+85
View File
@@ -100,6 +100,10 @@ class AXUtilitiesCollection:
return []
role_list = list(role_list)
if AXUtilitiesCollection._should_avoid_collection_for_roles(root, role_list):
return AXUtilitiesCollection._find_all_with_role_fallback(
root, role_list, role_match_type, pred)
tokens = ["AXUtilitiesCollection:", inspect.currentframe(),
"Root:", root, role_match_type, "of:", role_list]
debug.print_tokens(debug.LEVEL_INFO, tokens, True)
@@ -111,6 +115,87 @@ class AXUtilitiesCollection:
return matches
@staticmethod
def _find_all_with_role_fallback(
root: Atspi.Accessible,
role_list: list[Atspi.Role],
role_match_type: Atspi.CollectionMatchType,
pred: Callable[[Atspi.Accessible], bool] | None = None
) -> list[Atspi.Accessible]:
def matchesLandmarkRole(acc, role):
if role == Atspi.Role.LANDMARK:
return True
if AXUtilitiesRole.is_landmark_banner(acc):
return True
if AXUtilitiesRole.is_landmark_complementary(acc):
return True
if AXUtilitiesRole.is_landmark_contentinfo(acc):
return True
if AXUtilitiesRole.is_landmark_form(acc):
return True
if AXUtilitiesRole.is_landmark_main(acc):
return True
if AXUtilitiesRole.is_landmark_navigation(acc):
return True
if AXUtilitiesRole.is_landmark_region(acc):
return True
if AXUtilitiesRole.is_landmark_search(acc):
return True
return AXUtilitiesRole.is_dpub(acc)
def role_matches(acc):
role = AXObject.get_role(acc)
matched = role in role_list
if not matched and Atspi.Role.LANDMARK in role_list:
matched = matchesLandmarkRole(acc, role)
if role_match_type == Atspi.CollectionMatchType.NONE:
matched = not matched
if not matched:
return False
if pred is None:
return True
return pred(acc)
return AXObject.find_all_descendants(root, role_matches)
@staticmethod
def _should_avoid_collection_for_roles(
root: Atspi.Accessible,
role_list: list[Atspi.Role]
) -> bool:
if Atspi.Role.LANDMARK not in role_list:
return False
toolkit_name = AXObject.get_toolkit_name(root)
if toolkit_name in {"chromium", "chrome"}:
msg = (
"AXUtilitiesCollection: Avoiding collection interface for landmarks "
f"in Chromium-family app (toolkit={toolkit_name})."
)
debug.print_message(debug.LEVEL_INFO, msg, True)
return True
app = AXObject.get_application(root)
raw_name = AXObject.get_name(app) or ""
app_name = raw_name.lower()
chromium_apps = {
"brave",
"chrome",
"chromium",
"edge",
"opera",
"vivaldi",
}
if not any(name in app_name for name in chromium_apps):
return False
msg = (
"AXUtilitiesCollection: Avoiding collection interface for landmarks "
f"in Chromium-family app ({raw_name or 'unknown app'})."
)
debug.print_message(debug.LEVEL_INFO, msg, True)
return True
@staticmethod
def find_all_with_interfaces(
root: Atspi.Accessible,
+15
View File
@@ -806,15 +806,27 @@ class StructuralNavigation:
self.clearCache()
self._inModalDialog = inModalDialog
def filterZombies(matchList):
if not matchList:
return []
return [match for match in matchList if not self._script.utilities.isZombie(match)]
document = self._script.utilities.documentFrame()
cache = self._objectCache.get(hash(document), {})
key = f"{structuralNavigationObject.objType}:{arg}"
matches = cache.get(key, [])
if matches:
matches = filterZombies(matches)
if matches:
tokens = ["STRUCTURAL NAVIGATION: Returning", len(matches), "matches from cache"]
debug.printTokens(debug.LEVEL_INFO, tokens, True)
return matches.copy()
tokens = ["STRUCTURAL NAVIGATION: Cached matches are zombies; refreshing"]
debug.printTokens(debug.LEVEL_INFO, tokens, True)
cache.pop(key, None)
self._objectCache[hash(document)] = cache
if structuralNavigationObject.getter:
matches = structuralNavigationObject.getter(document, arg)
elif not structuralNavigationObject.criteria:
@@ -834,6 +846,7 @@ class StructuralNavigation:
"objects outside of modal dialog", modalDialog]
debug.printTokens(debug.LEVEL_INFO, tokens, True)
matches = filterZombies(matches)
rv = matches.copy()
cache[key] = matches
self._objectCache[hash(document)] = cache
@@ -899,6 +912,8 @@ class StructuralNavigation:
def _isValidMatch(obj):
if AXObject.is_dead(obj):
return False
if self._script.utilities.isZombie(obj):
return False
if self._script.utilities.isHidden(obj) or self._script.utilities.isEmpty(obj):
return False
if structuralNavigationObject.predicate is None: