Fixed crash bug with landmark navigation.
This commit is contained in:
@@ -100,6 +100,10 @@ class AXUtilitiesCollection:
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
role_list = list(role_list)
|
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(),
|
tokens = ["AXUtilitiesCollection:", inspect.currentframe(),
|
||||||
"Root:", root, role_match_type, "of:", role_list]
|
"Root:", root, role_match_type, "of:", role_list]
|
||||||
debug.print_tokens(debug.LEVEL_INFO, tokens, True)
|
debug.print_tokens(debug.LEVEL_INFO, tokens, True)
|
||||||
@@ -111,6 +115,87 @@ class AXUtilitiesCollection:
|
|||||||
|
|
||||||
return matches
|
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
|
@staticmethod
|
||||||
def find_all_with_interfaces(
|
def find_all_with_interfaces(
|
||||||
root: Atspi.Accessible,
|
root: Atspi.Accessible,
|
||||||
|
|||||||
@@ -806,14 +806,26 @@ class StructuralNavigation:
|
|||||||
self.clearCache()
|
self.clearCache()
|
||||||
self._inModalDialog = inModalDialog
|
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()
|
document = self._script.utilities.documentFrame()
|
||||||
cache = self._objectCache.get(hash(document), {})
|
cache = self._objectCache.get(hash(document), {})
|
||||||
key = f"{structuralNavigationObject.objType}:{arg}"
|
key = f"{structuralNavigationObject.objType}:{arg}"
|
||||||
matches = cache.get(key, [])
|
matches = cache.get(key, [])
|
||||||
if matches:
|
if matches:
|
||||||
tokens = ["STRUCTURAL NAVIGATION: Returning", len(matches), "matches from cache"]
|
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)
|
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
||||||
return matches.copy()
|
cache.pop(key, None)
|
||||||
|
self._objectCache[hash(document)] = cache
|
||||||
|
|
||||||
if structuralNavigationObject.getter:
|
if structuralNavigationObject.getter:
|
||||||
matches = structuralNavigationObject.getter(document, arg)
|
matches = structuralNavigationObject.getter(document, arg)
|
||||||
@@ -834,6 +846,7 @@ class StructuralNavigation:
|
|||||||
"objects outside of modal dialog", modalDialog]
|
"objects outside of modal dialog", modalDialog]
|
||||||
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
||||||
|
|
||||||
|
matches = filterZombies(matches)
|
||||||
rv = matches.copy()
|
rv = matches.copy()
|
||||||
cache[key] = matches
|
cache[key] = matches
|
||||||
self._objectCache[hash(document)] = cache
|
self._objectCache[hash(document)] = cache
|
||||||
@@ -899,6 +912,8 @@ class StructuralNavigation:
|
|||||||
def _isValidMatch(obj):
|
def _isValidMatch(obj):
|
||||||
if AXObject.is_dead(obj):
|
if AXObject.is_dead(obj):
|
||||||
return False
|
return False
|
||||||
|
if self._script.utilities.isZombie(obj):
|
||||||
|
return False
|
||||||
if self._script.utilities.isHidden(obj) or self._script.utilities.isEmpty(obj):
|
if self._script.utilities.isHidden(obj) or self._script.utilities.isEmpty(obj):
|
||||||
return False
|
return False
|
||||||
if structuralNavigationObject.predicate is None:
|
if structuralNavigationObject.predicate is None:
|
||||||
|
|||||||
Reference in New Issue
Block a user