Fixed crash bug with landmark navigation.
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user