diff --git a/tools/wptrunner/wptrunner/executors/executoratspi.py b/tools/wptrunner/wptrunner/executors/executoratspi.py index cd0cae5503b386..09259423592789 100644 --- a/tools/wptrunner/wptrunner/executors/executoratspi.py +++ b/tools/wptrunner/wptrunner/executors/executoratspi.py @@ -18,7 +18,7 @@ def find_active_tab(root): for relation in relationset: if relation.get_relation_type() == Atspi.RelationType.EMBEDS: return relation.get_target(0) - contiue + continue for i in range(Atspi.Accessible.get_child_count(node)): child = Atspi.Accessible.get_child_at_index(node, i) diff --git a/tools/wptrunner/wptrunner/executors/executoraxapi.py b/tools/wptrunner/wptrunner/executors/executoraxapi.py index 571aa525c36092..f864cbfebb1754 100644 --- a/tools/wptrunner/wptrunner/executors/executoraxapi.py +++ b/tools/wptrunner/wptrunner/executors/executoraxapi.py @@ -1,36 +1,99 @@ from ApplicationServices import ( - AXUIElementRef, AXUIElementCopyAttributeNames, AXUIElementCopyAttributeValue, - AXUIElementCopyParameterizedAttributeValue, - AXUIElementCopyParameterizedAttributeNames, - AXUIElementIsAttributeSettable, - AXUIElementCopyActionNames, - AXUIElementSetAttributeValue, AXUIElementCreateApplication, - AXUIElementCopyMultipleAttributeValues, - AXUIElementCopyActionDescription, - AXValueRef, - AXValueGetType, - kAXValueAXErrorType, ) +from Cocoa import ( + NSApplicationActivationPolicyRegular, + NSPredicate, + NSWorkspace, +) + +import json + +def find_browser(name): + ws = NSWorkspace.sharedWorkspace() + regular_predicate = NSPredicate.predicateWithFormat_(f"activationPolicy == {NSApplicationActivationPolicyRegular}") + running_apps = ws.runningApplications().filteredArrayUsingPredicate_(regular_predicate) + name_predicate = NSPredicate.predicateWithFormat_(f"localizedName contains[c] '{name}'") + filtered_apps = running_apps.filteredArrayUsingPredicate_(name_predicate) + if filtered_apps.count() == 0: + return None + app = filtered_apps[0] + pid = app.processIdentifier() + if pid == -1: + return None + return AXUIElementCreateApplication(pid) + + +def find_active_tab(browser): + stack = [browser] + tabs = [] + while stack: + node = stack.pop() + + (err, role) = AXUIElementCopyAttributeValue(node, "AXRole", None) + if err: + continue + if role == "AXWebArea": + return node + + (err, children) = AXUIElementCopyAttributeValue(node, "AXChildren", None) + if err: + continue + stack.extend(children) + + return None + + +def find_node(root, attribute, expected_value): + stack = [root] + while stack: + node = stack.pop() + + (err, attributes) = AXUIElementCopyAttributeNames(node, None) + if err: + continue + if attribute in attributes: + (err, value) = AXUIElementCopyAttributeValue(node, attribute, None) + if err: + continue + if value == expected_value: + return node + + (err, children) = AXUIElementCopyAttributeValue(node, "AXChildren", None) + if err: + continue + stack.extend(children) + return None + + +def serialize_node(node): + props = {} + props["API"] = "axapi" + (err, role) = AXUIElementCopyAttributeValue(node, "AXRole", None) + props["role"] = role + (err, name) = AXUIElementCopyAttributeValue(node, "AXTitle", None) + props["name"] = name + (err, description) = AXUIElementCopyAttributeValue(node, "AXDescription", None) + props["description"] = description + + return props + class AXAPIExecutorImpl: def setup(self, product_name): self.product_name = product_name + self.root = find_browser(self.product_name) + + if not self.root: + raise Exception(f"Couldn't find application: {product_name}") - def get_application_by_name(self, name): - # TODO: copied directly from https://github.com/eeejay/pyax/blob/main/src/pyax/_uielement.py - wl = CGWindowListCopyWindowInfo( - kCGWindowListExcludeDesktopElements, kCGNullWindowID - ) - for w in wl: - n = w.valueForKey_("kCGWindowOwnerName") - if name == w.valueForKey_("kCGWindowOwnerName"): - return AXUIElementCreateApplication( - int((w.valueForKey_("kCGWindowOwnerPID"))) - ) def get_accessibility_api_node(self, dom_id): - app = self.get_application_by_name(self.product_name) + tab = find_active_tab(self.root) + node = find_node(tab, "AXDOMIdentifier", dom_id) + if not node: + raise Exception(f"Couldn't find node with ID {dom_id}. Try passing --force-renderer-accessibility.") + return json.dumps(serialize_node(node))