diff --git a/core-aam/acacia/blockquote.html b/core-aam/acacia/blockquote.html new file mode 100644 index 00000000000000..77bd526822f11d --- /dev/null +++ b/core-aam/acacia/blockquote.html @@ -0,0 +1,32 @@ + + +core-aam: blockquote role + + + + + + + +
quote
+ + + diff --git a/core-aam/acacia/button.html b/core-aam/acacia/button.html new file mode 100644 index 00000000000000..0de9a457f8e029 --- /dev/null +++ b/core-aam/acacia/button.html @@ -0,0 +1,31 @@ + + +core-aam: role button + + + + + + + +
click me
+ + + diff --git a/resources/testdriver.js b/resources/testdriver.js index 2d1a89690cc25f..cdeb231bcf240c 100644 --- a/resources/testdriver.js +++ b/resources/testdriver.js @@ -1066,6 +1066,20 @@ */ clear_device_posture: function(context=null) { return window.test_driver_internal.clear_device_posture(context); + }, + + /** + * Get a serialized object representing the accessibility API's accessibility node. + * + * @param {id} id of element + * @returns {Promise} Fullfilled with object representing accessibilty node, + * rejected in the cases of failures. + */ + get_platform_accessibility_node: async function(dom_id) { + return window.test_driver_internal.get_platform_accessibility_node(dom_id, location.href) + .then((jsonresult) => { + return JSON.parse(jsonresult); + }); } }; @@ -1254,6 +1268,10 @@ async clear_device_posture(context=null) { throw new Error("clear_device_posture() is not implemented by testdriver-vendor.js"); + }, + + async get_platform_accessibility_node(dom_id, url) { + throw new Error("get_platform_accessibility_node() is not available."); } }; })(); diff --git a/tools/wpt/run.py b/tools/wpt/run.py index 2fb7f97f498c4a..6a7bf64fabd794 100644 --- a/tools/wpt/run.py +++ b/tools/wpt/run.py @@ -519,7 +519,8 @@ def setup_kwargs(self, kwargs): # We are on Taskcluster, where our Docker container does not have # enough capabilities to run Chrome with sandboxing. (gh-20133) kwargs["binary_args"].append("--no-sandbox") - + if kwargs["force_renderer_accessibility"]: + kwargs["binary_args"].append("--force-renderer-accessibility") class ContentShell(BrowserSetup): name = "content_shell" diff --git a/tools/wptrunner/wptrunner/executors/actions.py b/tools/wptrunner/wptrunner/executors/actions.py index 6e0c081b48f752..66e9e1a3c529d4 100644 --- a/tools/wptrunner/wptrunner/executors/actions.py +++ b/tools/wptrunner/wptrunner/executors/actions.py @@ -464,6 +464,19 @@ def __init__(self, logger, protocol): def __call__(self, payload): return self.protocol.device_posture.clear_device_posture() +class GetAccessibilityAPINodeAction: + name = "get_platform_accessibility_node" + + def __init__(self, logger, protocol): + self.logger = logger + self.protocol = protocol + + def __call__(self, payload): + dom_id = payload["dom_id"] + url = payload["url"] + return self.protocol.platform_accessibility.get_platform_accessibility_node(dom_id, url) + + actions = [ClickAction, DeleteAllCookiesAction, GetAllCookiesAction, @@ -499,4 +512,5 @@ def __call__(self, payload): RemoveVirtualSensorAction, GetVirtualSensorInformationAction, SetDevicePostureAction, - ClearDevicePostureAction] + ClearDevicePostureAction, + GetAccessibilityAPINodeAction] diff --git a/tools/wptrunner/wptrunner/executors/executoratspi.py b/tools/wptrunner/wptrunner/executors/executoratspi.py new file mode 100644 index 00000000000000..eabb5b13ab49b5 --- /dev/null +++ b/tools/wptrunner/wptrunner/executors/executoratspi.py @@ -0,0 +1,128 @@ +import gi + +gi.require_version("Atspi", "2.0") +from gi.repository import Atspi +import json +import threading +import time + +import sys + + +def poll_for_tab(root, product, url): + tab = find_tab(root, product, url) + while not tab: + time.sleep(0.01) + tab = find_tab(root, product, url) + + return tab + + +def find_tab(root, product, url): + stack = [root] + while stack: + node = stack.pop() + if Atspi.Accessible.get_role_name(node) == "frame": + relationset = Atspi.Accessible.get_relation_set(node) + for relation in relationset: + if relation.get_relation_type() == Atspi.RelationType.EMBEDS: + tab = relation.get_target(0) + if is_ready(tab, product, url): + return tab + else: + return None + continue + + for i in range(Atspi.Accessible.get_child_count(node)): + child = Atspi.Accessible.get_child_at_index(node, i) + stack.append(child) + + return None + + +def is_ready(tab, product, url): + # Firefox uses the "BUSY" state to indicate the page is not ready. + if product == "firefox": + state_set = Atspi.Accessible.get_state_set(tab) + return not Atspi.StateSet.contains(state_set, Atspi.StateType.BUSY) + + # Chromium family browsers do not use "BUSY", but you can + # tell if the document can be queried by URL attribute. If the 'URL' + # attribute is not here, we need to query for a new accessible object. + document = Atspi.Accessible.get_document_iface(tab) + document_attributes = Atspi.Document.get_document_attributes(document) + if "URI" in document_attributes and document_attributes["URI"] == url: + return True + return False + + +def serialize_node(node): + node_dictionary = {} + node_dictionary["API"] = "atspi" + node_dictionary["role"] = Atspi.Accessible.get_role_name(node) + node_dictionary["name"] = Atspi.Accessible.get_name(node) + node_dictionary["description"] = Atspi.Accessible.get_description(node) + + return node_dictionary + + +def find_node(root, dom_id): + stack = [root] + while stack: + node = stack.pop() + + attributes = Atspi.Accessible.get_attributes(node) + if "id" in attributes and attributes["id"] == dom_id: + return node + + for i in range(Atspi.Accessible.get_child_count(node)): + child = Atspi.Accessible.get_child_at_index(node, i) + stack.append(child) + + return None + + +def find_browser(name): + desktop = Atspi.get_desktop(0) + child_count = Atspi.Accessible.get_child_count(desktop) + for i in range(child_count): + app = Atspi.Accessible.get_child_at_index(desktop, i) + full_app_name = Atspi.Accessible.get_name(app) + if name in full_app_name.lower(): + return (app, full_app_name) + return (None, None) + + +class AtspiExecutorImpl: + def setup(self, product_name, logger): + self.logger = logger + self.product_name = product_name + self.full_app_name = "" + self.root = None + self.document = None + self.test_url = None + + (self.root, self.full_app_name) = find_browser(self.product_name) + if not self.root: + self.logger.error( + f"Couldn't find browser {self.product_name} in accessibility API ATSPI. Accessibility API queries will not succeeded." + ) + + + def get_platform_accessibility_node(self, dom_id, url): + if not self.root: + raise Exception( + f"Couldn't find browser {self.product_name} in accessibility API ATSPI. Did you turn on accessibility?" + ) + + if self.test_url != url or not self.document: + self.test_url = url + self.document = poll_for_tab(self.root, self.product_name, url) + + node = find_node(self.document, dom_id) + if not node: + raise Exception( + f"Couldn't find node with id={dom_id} in accessibility API ATSPI." + ) + + return json.dumps(serialize_node(node)) diff --git a/tools/wptrunner/wptrunner/executors/executoraxapi.py b/tools/wptrunner/wptrunner/executors/executoraxapi.py new file mode 100644 index 00000000000000..f6d724c8124114 --- /dev/null +++ b/tools/wptrunner/wptrunner/executors/executoraxapi.py @@ -0,0 +1,130 @@ +from ApplicationServices import ( + AXUIElementCopyAttributeNames, + AXUIElementCopyAttributeValue, + AXUIElementCreateApplication, + AXUIElementSetAttributeValue, +) + +from Cocoa import ( + NSApplicationActivationPolicyRegular, + NSPredicate, + NSWorkspace, +) + +import json +import time + +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 + browser = AXUIElementCreateApplication(pid) + activate_accessibility(browser) + return browser + +def activate_accessibility(browser): + AXUIElementSetAttributeValue(browser, "AXEnhancedUserInterface", 1) + +def poll_for_tab(root, url): + tab = find_tab(root, url) + loops = 0 + while not tab: + loops += 1 + time.sleep(0.01) + tab = find_tab(root, url) + + return tab + +def find_tab(root, url): + stack = [root] + tabs = [] + while stack: + node = stack.pop() + + (err, role) = AXUIElementCopyAttributeValue(node, "AXRole", None) + if err: + continue + if role == "AXWebArea": + (err, tab_url) = AXUIElementCopyAttributeValue(node, "AXURL", None) + # tab_url is a NSURL object and must be converted to string. + if not err and str(tab_url) == url: + return node + else: + continue + + (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 = None + self.document = None + self.test_url = None + + + def get_platform_accessibility_node(self, dom_id, url): + if not self.root: + self.root = find_browser(self.product_name) + if not self.root: + raise Exception( + f"Couldn't find browser {self.product_name} in accessibility API: AX API. Did you turn on accessibility?" + ) + + if self.test_url != url or not self.document: + self.test_url = url + self.document = poll_for_tab(self.root, url) + + node = find_node(self.document, "AXDOMIdentifier", dom_id) + if not node: + raise Exception(f"Couldn't find node with ID {dom_id}.") + return json.dumps(serialize_node(node)) diff --git a/tools/wptrunner/wptrunner/executors/executormarionette.py b/tools/wptrunner/wptrunner/executors/executormarionette.py index 05a9fc1ae4b874..fae212645c2a49 100644 --- a/tools/wptrunner/wptrunner/executors/executormarionette.py +++ b/tools/wptrunner/wptrunner/executors/executormarionette.py @@ -24,6 +24,7 @@ WdspecExecutor, get_pages, strip_server) + from .protocol import (AccessibilityProtocolPart, ActionSequenceProtocolPart, AssertsProtocolPart, @@ -48,6 +49,7 @@ DevicePostureProtocolPart, merge_dicts) +from .executorplatformaccessibility import (PlatformAccessibilityProtocolPart) def do_delayed_imports(): global errors, marionette, Addons, WebAuthn @@ -782,12 +784,14 @@ class MarionetteProtocol(Protocol): MarionetteDebugProtocolPart, MarionetteAccessibilityProtocolPart, MarionetteVirtualSensorProtocolPart, - MarionetteDevicePostureProtocolPart] + MarionetteDevicePostureProtocolPart, + PlatformAccessibilityProtocolPart] def __init__(self, executor, browser, capabilities=None, timeout_multiplier=1, e10s=True, ccov=False): do_delayed_imports() super().__init__(executor, browser) + self.product_name = browser.product_name self.marionette = None self.marionette_port = browser.marionette_port self.capabilities = capabilities diff --git a/tools/wptrunner/wptrunner/executors/executorplatformaccessibility.py b/tools/wptrunner/wptrunner/executors/executorplatformaccessibility.py new file mode 100644 index 00000000000000..b2e467b7cf982e --- /dev/null +++ b/tools/wptrunner/wptrunner/executors/executorplatformaccessibility.py @@ -0,0 +1,37 @@ +from .protocol import ProtocolPart + +from abc import ABCMeta +from sys import platform + +linux = False +mac = False +windows = False +if platform == "linux": + linux = True + from .executoratspi import * +if platform == "darwin": + mac = True + from .executoraxapi import * +if platform == "win32": + windows = True + from .executorwindowsaccessibility import WindowsAccessibilityExecutorImpl + +class PlatformAccessibilityProtocolPart(ProtocolPart): + """Protocol part for platform accessibility introspection""" + name = "platform_accessibility" + + def setup(self): + self.product_name = self.parent.product_name + self.impl = None + if linux: + self.impl = AtspiExecutorImpl() + self.impl.setup(self.product_name, self.logger) + if mac: + self.impl = AXAPIExecutorImpl() + self.impl.setup(self.product_name) + if windows: + self.impl = WindowsAccessibilityExecutorImpl() + self.impl.setup(self.product_name) + + def get_platform_accessibility_node(self, dom_id, url): + return self.impl.get_platform_accessibility_node(dom_id, url) diff --git a/tools/wptrunner/wptrunner/executors/executorwebdriver.py b/tools/wptrunner/wptrunner/executors/executorwebdriver.py index 69013e5e796979..2664e3dc68f4da 100644 --- a/tools/wptrunner/wptrunner/executors/executorwebdriver.py +++ b/tools/wptrunner/wptrunner/executors/executorwebdriver.py @@ -38,6 +38,8 @@ DevicePostureProtocolPart, merge_dicts) +from .executorplatformaccessibility import (PlatformAccessibilityProtocolPart) + from webdriver.client import Session from webdriver import error @@ -462,10 +464,12 @@ class WebDriverProtocol(Protocol): WebDriverFedCMProtocolPart, WebDriverDebugProtocolPart, WebDriverVirtualSensorPart, - WebDriverDevicePostureProtocolPart] + WebDriverDevicePostureProtocolPart, + PlatformAccessibilityProtocolPart] def __init__(self, executor, browser, capabilities, **kwargs): super().__init__(executor, browser) + self.product_name = browser.product_name self.capabilities = capabilities if hasattr(browser, "capabilities"): if self.capabilities is None: diff --git a/tools/wptrunner/wptrunner/executors/executorwindowsaccessibility.py b/tools/wptrunner/wptrunner/executors/executorwindowsaccessibility.py new file mode 100644 index 00000000000000..a1e336a215831a --- /dev/null +++ b/tools/wptrunner/wptrunner/executors/executorwindowsaccessibility.py @@ -0,0 +1,127 @@ +from ..executors.ia2.constants import * + +import json +import sys +import time + +import ctypes +from ctypes import POINTER, byref +from ctypes.wintypes import BOOL, HWND, LPARAM, POINT + +import comtypes.client +from comtypes import COMError, IServiceProvider + +CHILDID_SELF = 0 +OBJID_CLIENT = -4 + +user32 = ctypes.windll.user32 +oleacc = ctypes.oledll.oleacc +oleaccMod = comtypes.client.GetModule("oleacc.dll") +IAccessible = oleaccMod.IAccessible + +# CoCreateInstance of UIA also initializes IA2 +uiaMod = comtypes.client.GetModule("UIAutomationCore.dll") +uiaClient = comtypes.CoCreateInstance( + uiaMod.CUIAutomation._reg_clsid_, + interface=uiaMod.IUIAutomation, + clsctx=comtypes.CLSCTX_INPROC_SERVER, +) + +def accessible_object_from_window(hwnd): + p = POINTER(IAccessible)() + oleacc.AccessibleObjectFromWindow( + hwnd, OBJID_CLIENT, byref(IAccessible._iid_), byref(p) + ) + return p + +def name_from_hwnd(hwnd): + MAX_CHARS = 257 + buffer = ctypes.create_unicode_buffer(MAX_CHARS) + user32.GetWindowTextW(hwnd, buffer, MAX_CHARS) + return buffer.value + +def get_browser_hwnd(product_name): + found = [] + + @ctypes.WINFUNCTYPE(BOOL, HWND, LPARAM) + def check_window_name(hwnd, lParam): + window_name = name_from_hwnd(hwnd) + if product_name not in window_name.lower(): + # EnumWindows should continue enumerating + return True + found.append(hwnd) + # EnumWindows should stop enumerating (since we found the right window) + return False + + user32.EnumWindows(check_window_name, LPARAM(0)) + if not found: + raise LookupError(f"Couldn't find {product_name} HWND") + return found[0] + +def to_ia2(node): + service = node.QueryInterface(IServiceProvider) + return service.QueryService(IAccessible2._iid_, IAccessible2) + +def find_browser(product_name): + hwnd = get_browser_hwnd(product_name) + root = accessible_object_from_window(hwnd) + return to_ia2(root) + +def poll_for_tab(url, root): + tab = find_tab(url, root) + while not tab: + time.sleep(0.01) + tab = find_tab(url, root) + return tab + +def find_tab(url, root): + for i in range(1, root.accChildCount + 1): + child = to_ia2(root.accChild(i)) + if child.accRole(CHILDID_SELF) == ROLE_SYSTEM_DOCUMENT: + if child.accValue(CHILDID_SELF) == url: + return child + # No need to search within documents. + return + descendant = find_tab(url, child) + if descendant: + return descendant + +def find_ia2_node(root, id): + id_attribute = f"id:{id};" + for i in range(1, root.accChildCount + 1): + child = to_ia2(root.accChild(i)) + if child.attributes and id_attribute in child.attributes: + return child + descendant = find_ia2_node(child, id) + if descendant: + return descendant + +def serialize_node(node): + node_dictionary = {} + node_dictionary["API"] = "windows" + + # MSAA properties + node_dictionary["name"] = node.accName(CHILDID_SELF) + node_dictionary["msaa_role"] = role_to_string[node.accRole(CHILDID_SELF)] + node_dictionary["msaa_states"] = get_msaa_state_list(node.accState(CHILDID_SELF)) + + # IAccessible2 properties + node_dictionary["ia2_role"] = role_to_string[node.role()] + node_dictionary["ia2_states"] = get_state_list(node.states) + + return node_dictionary + +class WindowsAccessibilityExecutorImpl: + def setup(self, product_name): + self.product_name = product_name + + def get_platform_accessibility_node(self, dom_id, url): + self.root = find_browser(self.product_name) + if not self.root: + raise Exception(f"Couldn't find browser {self.product_name}.") + + tab = poll_for_tab(url, self.root) + node = find_ia2_node(tab, dom_id) + if not node: + raise Exception(f"Couldn't find node with ID {dom_id}.") + return json.dumps(serialize_node(node)) diff --git a/tools/wptrunner/wptrunner/executors/ia2/constants.py b/tools/wptrunner/wptrunner/executors/ia2/constants.py new file mode 100644 index 00000000000000..73b7ff2d3a7642 --- /dev/null +++ b/tools/wptrunner/wptrunner/executors/ia2/constants.py @@ -0,0 +1,344 @@ +import os +import comtypes.client + +# Get IAccessible2 constants for helper functions below +ia2Tlb = os.path.join( + os.path.dirname(os.path.realpath(__file__)), + "ia2_api_all.tlb", +) +ia2Mod = comtypes.client.GetModule(ia2Tlb) +# Add to globals the many IAccessible2 constants. These will also be imported +# when this file is imported. +globals().update((k, getattr(ia2Mod, k)) for k in ia2Mod.__all__) + +# Some constants are not provided via oleacc, specifically those for MSAA +ROLE_SYSTEM_TITLEBAR=1 +ROLE_SYSTEM_MENUBAR=2 +ROLE_SYSTEM_SCROLLBAR=3 +ROLE_SYSTEM_GRIP=4 +ROLE_SYSTEM_SOUND=5 +ROLE_SYSTEM_CURSOR=6 +ROLE_SYSTEM_CARET=7 +ROLE_SYSTEM_ALERT=8 +ROLE_SYSTEM_WINDOW=9 +ROLE_SYSTEM_CLIENT=10 +ROLE_SYSTEM_MENUPOPUP=11 +ROLE_SYSTEM_MENUITEM=12 +ROLE_SYSTEM_TOOLTIP=13 +ROLE_SYSTEM_APPLICATION=14 +ROLE_SYSTEM_DOCUMENT=15 +ROLE_SYSTEM_PANE=16 +ROLE_SYSTEM_CHART=17 +ROLE_SYSTEM_DIALOG=18 +ROLE_SYSTEM_BORDER=19 +ROLE_SYSTEM_GROUPING=20 +ROLE_SYSTEM_SEPARATOR=21 +ROLE_SYSTEM_TOOLBAR=22 +ROLE_SYSTEM_STATUSBAR=23 +ROLE_SYSTEM_TABLE=24 +ROLE_SYSTEM_COLUMNHEADER=25 +ROLE_SYSTEM_ROWHEADER=26 +ROLE_SYSTEM_COLUMN=27 +ROLE_SYSTEM_ROW=28 +ROLE_SYSTEM_CELL=29 +ROLE_SYSTEM_LINK=30 +ROLE_SYSTEM_HELPBALLOON=31 +ROLE_SYSTEM_CHARACTER=32 +ROLE_SYSTEM_LIST=33 +ROLE_SYSTEM_LISTITEM=34 +ROLE_SYSTEM_OUTLINE=35 +ROLE_SYSTEM_OUTLINEITEM=36 +ROLE_SYSTEM_PAGETAB=37 +ROLE_SYSTEM_PROPERTYPAGE=38 +ROLE_SYSTEM_INDICATOR=39 +ROLE_SYSTEM_GRAPHIC=40 +ROLE_SYSTEM_STATICTEXT=41 +ROLE_SYSTEM_TEXT=42 +ROLE_SYSTEM_PUSHBUTTON=43 +ROLE_SYSTEM_CHECKBUTTON=44 +ROLE_SYSTEM_RADIOBUTTON=45 +ROLE_SYSTEM_COMBOBOX=46 +ROLE_SYSTEM_DROPLIST=47 +ROLE_SYSTEM_PROGRESSBAR=48 +ROLE_SYSTEM_DIAL=49 +ROLE_SYSTEM_HOTKEYFIELD=50 +ROLE_SYSTEM_SLIDER=51 +ROLE_SYSTEM_SPINBUTTON=52 +ROLE_SYSTEM_DIAGRAM=53 +ROLE_SYSTEM_ANIMATION=54 +ROLE_SYSTEM_EQUATION=55 +ROLE_SYSTEM_BUTTONDROPDOWN=56 +ROLE_SYSTEM_BUTTONMENU=57 +ROLE_SYSTEM_BUTTONDROPDOWNGRID=58 +ROLE_SYSTEM_WHITESPACE=59 +ROLE_SYSTEM_PAGETABLIST=60 +ROLE_SYSTEM_CLOCK=61 +ROLE_SYSTEM_SPLITBUTTON=62 +ROLE_SYSTEM_IPADDRESS=63 +ROLE_SYSTEM_OUTLINEBUTTON=64 + +role_to_string = { + ROLE_SYSTEM_TITLEBAR: "ROLE_SYSTEM_TITLEBAR", + ROLE_SYSTEM_MENUBAR: "ROLE_SYSTEM_MENUBAR", + ROLE_SYSTEM_SCROLLBAR: "ROLE_SYSTEM_SCROLLBAR", + ROLE_SYSTEM_GRIP: "ROLE_SYSTEM_GRIP", + ROLE_SYSTEM_SOUND: "ROLE_SYSTEM_SOUND", + ROLE_SYSTEM_CURSOR: "ROLE_SYSTEM_CURSOR", + ROLE_SYSTEM_CARET: "ROLE_SYSTEM_CARET", + ROLE_SYSTEM_ALERT: "ROLE_SYSTEM_ALERT", + ROLE_SYSTEM_WINDOW: "ROLE_SYSTEM_WINDOW", + ROLE_SYSTEM_CLIENT: "ROLE_SYSTEM_CLIENT", + ROLE_SYSTEM_MENUPOPUP: "ROLE_SYSTEM_MENUPOPUP", + ROLE_SYSTEM_MENUITEM: "ROLE_SYSTEM_MENUITEM", + ROLE_SYSTEM_TOOLTIP: "ROLE_SYSTEM_TOOLTIP", + ROLE_SYSTEM_APPLICATION: "ROLE_SYSTEM_APPLICATION", + ROLE_SYSTEM_DOCUMENT: "ROLE_SYSTEM_DOCUMENT", + ROLE_SYSTEM_PANE: "ROLE_SYSTEM_PANE", + ROLE_SYSTEM_CHART: "ROLE_SYSTEM_CHART", + ROLE_SYSTEM_DIALOG: "ROLE_SYSTEM_DIALOG", + ROLE_SYSTEM_BORDER: "ROLE_SYSTEM_BORDER", + ROLE_SYSTEM_GROUPING: "ROLE_SYSTEM_GROUPING", + ROLE_SYSTEM_SEPARATOR: "ROLE_SYSTEM_SEPARATOR", + ROLE_SYSTEM_TOOLBAR: "ROLE_SYSTEM_TOOLBAR", + ROLE_SYSTEM_STATUSBAR: "ROLE_SYSTEM_STATUSBAR", + ROLE_SYSTEM_TABLE: "ROLE_SYSTEM_TABLE", + ROLE_SYSTEM_COLUMNHEADER: "ROLE_SYSTEM_COLUMNHEADER", + ROLE_SYSTEM_ROWHEADER: "ROLE_SYSTEM_ROWHEADER", + ROLE_SYSTEM_COLUMN: "ROLE_SYSTEM_COLUMN", + ROLE_SYSTEM_ROW: "ROLE_SYSTEM_ROW", + ROLE_SYSTEM_CELL: "ROLE_SYSTEM_CEL", + ROLE_SYSTEM_LINK: "ROLE_SYSTEM_LINK", + ROLE_SYSTEM_HELPBALLOON: "ROLE_SYSTEM_HELPBALLOON", + ROLE_SYSTEM_CHARACTER: "ROLE_SYSTEM_CHARACTER", + ROLE_SYSTEM_LIST: "ROLE_SYSTEM_LIST", + ROLE_SYSTEM_LISTITEM: "ROLE_SYSTEM_LISTITEM", + ROLE_SYSTEM_OUTLINE: "ROLE_SYSTEM_OUTLINE", + ROLE_SYSTEM_OUTLINEITEM: "ROLE_SYSTEM_OUTLINEITEM", + ROLE_SYSTEM_PAGETAB: "ROLE_SYSTEM_PAGETAB", + ROLE_SYSTEM_PROPERTYPAGE: "ROLE_SYSTEM_PROPERTYPAGE", + ROLE_SYSTEM_INDICATOR: "ROLE_SYSTEM_INDICATOR", + ROLE_SYSTEM_GRAPHIC: "ROLE_SYSTEM_GRAPHIC", + ROLE_SYSTEM_STATICTEXT: "ROLE_SYSTEM_STATICTEXT", + ROLE_SYSTEM_TEXT: "ROLE_SYSTEM_TEXT", + ROLE_SYSTEM_PUSHBUTTON: "ROLE_SYSTEM_PUSHBUTTON", + ROLE_SYSTEM_CHECKBUTTON: "ROLE_SYSTEM_CHECKBUTTON", + ROLE_SYSTEM_RADIOBUTTON: "ROLE_SYSTEM_RADIOBUTTON", + ROLE_SYSTEM_COMBOBOX: "ROLE_SYSTEM_COMBOBOX", + ROLE_SYSTEM_DROPLIST: "ROLE_SYSTEM_DROPLIST", + ROLE_SYSTEM_PROGRESSBAR: "ROLE_SYSTEM_PROGRESSBAR", + ROLE_SYSTEM_DIAL: "ROLE_SYSTEM_DIAL", + ROLE_SYSTEM_HOTKEYFIELD: "ROLE_SYSTEM_HOTKEYFIELD", + ROLE_SYSTEM_SLIDER: "ROLE_SYSTEM_SLIDER", + ROLE_SYSTEM_SPINBUTTON: "ROLE_SYSTEM_SPINBUTTON", + ROLE_SYSTEM_DIAGRAM: "ROLE_SYSTEM_DIAGRAM", + ROLE_SYSTEM_ANIMATION: "ROLE_SYSTEM_ANIMATION", + ROLE_SYSTEM_EQUATION: "ROLE_SYSTEM_EQUATION", + ROLE_SYSTEM_BUTTONDROPDOWN: "ROLE_SYSTEM_BUTTONDROPDOWN", + ROLE_SYSTEM_BUTTONMENU: "ROLE_SYSTEM_BUTTONMENU", + ROLE_SYSTEM_BUTTONDROPDOWNGRID: "ROLE_SYSTEM_BUTTONDROPDOWNGRID", + ROLE_SYSTEM_WHITESPACE: "ROLE_SYSTEM_WHITESPACE", + ROLE_SYSTEM_PAGETABLIST: "ROLE_SYSTEM_PAGETABLIST", + ROLE_SYSTEM_CLOCK: "ROLE_SYSTEM_CLOCK", + ROLE_SYSTEM_SPLITBUTTON: "ROLE_SYSTEM_SPLITBUTTON", + ROLE_SYSTEM_IPADDRESS: "ROLE_SYSTEM_IPADDRESS", + ROLE_SYSTEM_OUTLINEBUTTON: "ROLE_SYSTEM_OUTLINEBUTTON", + IA2_ROLE_CANVAS: "IA2_ROLE_CANVAS", + IA2_ROLE_CAPTION: "IA2_ROLE_CAPTION", + IA2_ROLE_CHECK_MENU_ITEM: "IA2_ROLE_CHECK_MENU_ITEM", + IA2_ROLE_COLOR_CHOOSER: "IA2_ROLE_COLOR_CHOOSER", + IA2_ROLE_DATE_EDITOR: "IA2_ROLE_DATE_EDITOR", + IA2_ROLE_DESKTOP_ICON: "IA2_ROLE_DESKTOP_ICON", + IA2_ROLE_DESKTOP_PANE: "IA2_ROLE_DESKTOP_PANE", + IA2_ROLE_DIRECTORY_PANE: "IA2_ROLE_DIRECTORY_PANE", + IA2_ROLE_EDITBAR: "IA2_ROLE_EDITBAR", + IA2_ROLE_EMBEDDED_OBJECT: "IA2_ROLE_EMBEDDED_OBJECT", + IA2_ROLE_ENDNOTE: "IA2_ROLE_ENDNOTE", + IA2_ROLE_FILE_CHOOSER: "IA2_ROLE_FILE_CHOOSER", + IA2_ROLE_FONT_CHOOSER: "IA2_ROLE_FONT_CHOOSER", + IA2_ROLE_FOOTER: "IA2_ROLE_FOOTER", + IA2_ROLE_FOOTNOTE: "IA2_ROLE_FOOTNOTE", + IA2_ROLE_FORM: "IA2_ROLE_FORM", + IA2_ROLE_FRAME: "IA2_ROLE_FRAME", + IA2_ROLE_GLASS_PANE: "IA2_ROLE_GLASS_PANE", + IA2_ROLE_HEADER: "IA2_ROLE_HEADER", + IA2_ROLE_HEADING: "IA2_ROLE_HEADING", + IA2_ROLE_ICON: "IA2_ROLE_ICON", + IA2_ROLE_IMAGE_MAP: "IA2_ROLE_IMAGE_MAP", + IA2_ROLE_INPUT_METHOD_WINDOW: "IA2_ROLE_INPUT_METHOD_WINDOW", + IA2_ROLE_INTERNAL_FRAME: "IA2_ROLE_INTERNAL_FRAME", + IA2_ROLE_LABEL: "IA2_ROLE_LABEL", + IA2_ROLE_LAYERED_PANE: "IA2_ROLE_LAYERED_PANE", + IA2_ROLE_NOTE: "IA2_ROLE_NOTE", + IA2_ROLE_OPTION_PANE: "IA2_ROLE_OPTION_PANE", + IA2_ROLE_PAGE: "IA2_ROLE_PAGE", + IA2_ROLE_PARAGRAPH: "IA2_ROLE_PARAGRAPH", + IA2_ROLE_RADIO_MENU_ITEM: "IA2_ROLE_RADIO_MENU_ITEM", + IA2_ROLE_REDUNDANT_OBJECT: "IA2_ROLE_REDUNDANT_OBJECT", + IA2_ROLE_ROOT_PANE: "IA2_ROLE_ROOT_PANE", + IA2_ROLE_RULER: "IA2_ROLE_RULER", + IA2_ROLE_SCROLL_PANE: "IA2_ROLE_SCROLL_PANE", + IA2_ROLE_SECTION: "IA2_ROLE_SECTION", + IA2_ROLE_SHAPE: "IA2_ROLE_SHAPE", + IA2_ROLE_SPLIT_PANE: "IA2_ROLE_SPLIT_PANE", + IA2_ROLE_TEAR_OFF_MENU: "IA2_ROLE_TEAR_OFF_MENU", + IA2_ROLE_TERMINAL: "IA2_ROLE_TERMINAL", + IA2_ROLE_TEXT_FRAME: "IA2_ROLE_TEXT_FRAME", + IA2_ROLE_TOGGLE_BUTTON: "IA2_ROLE_TOGGLE_BUTTON", + IA2_ROLE_UNKNOWN: "IA2_ROLE_UNKNOWN", + IA2_ROLE_VIEW_PORT: "IA2_ROLE_VIEW_PORT", + IA2_ROLE_COMPLEMENTARY_CONTENT: "IA2_ROLE_COMPLEMENTARY_CONTENT", + IA2_ROLE_LANDMARK: "IA2_ROLE_LANDMARK", + IA2_ROLE_LEVEL_BAR: "IA2_ROLE_LEVEL_BAR", + IA2_ROLE_CONTENT_DELETION: "IA2_ROLE_CONTENT_DELETION", + IA2_ROLE_CONTENT_INSERTION: "IA2_ROLE_CONTENT_INSERTION", + IA2_ROLE_BLOCK_QUOTE: "IA2_ROLE_BLOCK_QUOTE", + IA2_ROLE_MARK: "IA2_ROLE_MARK", + IA2_ROLE_SUGGESTION: "IA2_ROLE_SUGGESTION", + IA2_ROLE_COMMENT: "IA2_ROLE_COMMENT" +} + +STATE_SYSTEM_UNAVAILABLE = 0x00000001 +STATE_SYSTEM_SELECTED = 0x00000002 +STATE_SYSTEM_FOCUSED = 0x00000004 +STATE_SYSTEM_PRESSED = 0x00000008 +STATE_SYSTEM_CHECKED = 0x00000010 +STATE_SYSTEM_MIXED = 0x00000020 +STATE_SYSTEM_INDETERMINATE = STATE_SYSTEM_MIXED +STATE_SYSTEM_READONLY = 0x00000040 +STATE_SYSTEM_HOTTRACKED = 0x00000080 +STATE_SYSTEM_DEFAULT = 0x00000100 +STATE_SYSTEM_EXPANDED = 0x00000200 +STATE_SYSTEM_COLLAPSED = 0x00000400 +STATE_SYSTEM_BUSY = 0x00000800 +STATE_SYSTEM_FLOATING = 0x00001000 +STATE_SYSTEM_MARQUEED = 0x00002000 +STATE_SYSTEM_ANIMATED = 0x00004000 +STATE_SYSTEM_INVISIBLE = 0x00008000 +STATE_SYSTEM_OFFSCREEN = 0x00010000 +STATE_SYSTEM_SIZEABLE = 0x00020000 +STATE_SYSTEM_MOVEABLE = 0x00040000 +STATE_SYSTEM_SELFVOICING = 0x00080000 +STATE_SYSTEM_FOCUSABLE = 0x00100000 +STATE_SYSTEM_SELECTABLE = 0x00200000 +STATE_SYSTEM_LINKED = 0x00400000 +STATE_SYSTEM_TRAVERSED = 0x00800000 +STATE_SYSTEM_MULTISELECTABLE = 0x01000000 +STATE_SYSTEM_EXTSELECTABLE = 0x02000000 +STATE_SYSTEM_ALERT_LOW = 0x04000000 +STATE_SYSTEM_ALERT_MEDIUM = 0x08000000 +STATE_SYSTEM_ALERT_HIGH = 0x10000000 +STATE_SYSTEM_PROTECTED = 0x20000000 +STATE_SYSTEM_HASPOPUP = 0x40000000 +STATE_SYSTEM_VALID = 0x3FFFFFFF + +def get_msaa_state_list(states): + state_strings = [] + if states & STATE_SYSTEM_ALERT_HIGH: + state_strings.append("ALERT_HIGH") + if states & STATE_SYSTEM_ALERT_MEDIUM: + state_strings.append("ALERT_MEDIUM") + if states & STATE_SYSTEM_ALERT_LOW: + state_strings.append("ALERT_LOW") + if states & STATE_SYSTEM_ANIMATED: + state_strings.append("ANIMATED") + if states & STATE_SYSTEM_BUSY: + state_strings.append("BUSY") + if states & STATE_SYSTEM_CHECKED: + state_strings.append("CHECKED") + if states & STATE_SYSTEM_COLLAPSED: + state_strings.append("COLLAPSED") + if states & STATE_SYSTEM_DEFAULT: + state_strings.append("DEFAULT") + if states & STATE_SYSTEM_EXPANDED: + state_strings.append("EXPANDED") + if states & STATE_SYSTEM_EXTSELECTABLE: + state_strings.append("EXTSELECTABLE") + if states & STATE_SYSTEM_FLOATING: + state_strings.append("FLOATING") + if states & STATE_SYSTEM_FOCUSABLE: + state_strings.append("FOCUSABLE") + if states & STATE_SYSTEM_FOCUSED: + state_strings.append("FOCUSED") + if states & STATE_SYSTEM_HASPOPUP: + state_strings.append("HASPOPUP") + if states & STATE_SYSTEM_HOTTRACKED: + state_strings.append("HOTTRACKED") + if states & STATE_SYSTEM_INVISIBLE: + state_strings.append("INVISIBLE") + if states & STATE_SYSTEM_LINKED: + state_strings.append("LINKED") + if states & STATE_SYSTEM_MARQUEED: + state_strings.append("MARQUEED") + if states & STATE_SYSTEM_MIXED: + state_strings.append("MIXED") + if states & STATE_SYSTEM_MOVEABLE: + state_strings.append("MOVEABLE") + if states & STATE_SYSTEM_MULTISELECTABLE: + state_strings.append("MULTISELECTABLE") + if states & STATE_SYSTEM_OFFSCREEN: + state_strings.append("OFFSCREEN") + if states & STATE_SYSTEM_PRESSED: + state_strings.append("PRESSED") + if states & STATE_SYSTEM_PROTECTED: + state_strings.append("PROTECTED") + if states & STATE_SYSTEM_READONLY: + state_strings.append("READONLY") + if states & STATE_SYSTEM_SELECTABLE: + state_strings.append("SELECTABLE") + if states & STATE_SYSTEM_SELECTED: + state_strings.append("SELECTED") + if states & STATE_SYSTEM_SELFVOICING: + state_strings.append("SELFVOICING") + if states & STATE_SYSTEM_SIZEABLE: + state_strings.append("SIZEABLE") + if states & STATE_SYSTEM_TRAVERSED: + state_strings.append("TRAVERSED") + if states & STATE_SYSTEM_UNAVAILABLE: + state_strings.append("UNAVAILABLE") + + return state_strings + +def get_state_list(states): + state_strings = [] + if states & IA2_STATE_ACTIVE: + state_strings.append("ACTIVE") + if states & IA2_STATE_ARMED: + state_strings.append("ARMED") + if states & IA2_STATE_CHECKABLE: + state_strings.append("CHECKABLE") + if states & IA2_STATE_DEFUNCT: + state_strings.append("DEFUNCT") + if states & IA2_STATE_EDITABLE: + state_strings.append("EDITABLE") + if states & IA2_STATE_HORIZONTAL: + state_strings.append("HORIZONTAL") + if states & IA2_STATE_ICONIFIED: + state_strings.append("ICONIFIED") + if states & IA2_STATE_INVALID_ENTRY: + state_strings.append("INVALID_ENTRY") + if states & IA2_STATE_MANAGES_DESCENDANTS: + state_strings.append("MANAGES_DESCENDANTS") + if states & IA2_STATE_MODAL: + state_strings.append("MODAL") + if states & IA2_STATE_MULTI_LINE: + state_strings.append("MULTI_LINE") + if states & IA2_STATE_OPAQUE: + state_strings.append("OPAQUE") + if states & IA2_STATE_PINNED: + state_strings.append("PINNED") + if states & IA2_STATE_REQUIRED: + state_strings.append("REQUIRED") + if states & IA2_STATE_SELECTABLE_TEXT: + state_strings.append("SELECTABLE_TEXT") + if states & IA2_STATE_SINGLE_LINE: + state_strings.append("SINGLE_LINE") + if states & IA2_STATE_STALE: + state_strings.append("STALE") + if states & IA2_STATE_SUPPORTS_AUTOCOMPLETION: + state_strings.append("SUPPORTS_AUTOCOMPLETION") + if states & IA2_STATE_TRANSIENT: + state_strings.append("TRANSIENT") + if states & IA2_STATE_VERTICAL: + state_strings.append("VERTICAL") + + return state_strings diff --git a/tools/wptrunner/wptrunner/executors/ia2/ia2_api_all.tlb b/tools/wptrunner/wptrunner/executors/ia2/ia2_api_all.tlb new file mode 100644 index 00000000000000..449c10b258dd7b Binary files /dev/null and b/tools/wptrunner/wptrunner/executors/ia2/ia2_api_all.tlb differ diff --git a/tools/wptrunner/wptrunner/testdriver-extra.js b/tools/wptrunner/wptrunner/testdriver-extra.js index 87d3826bfceb6a..ac0bb3afe3a119 100644 --- a/tools/wptrunner/wptrunner/testdriver-extra.js +++ b/tools/wptrunner/wptrunner/testdriver-extra.js @@ -335,4 +335,8 @@ window.test_driver_internal.clear_device_posture = function(context=null) { return create_action("clear_device_posture", {context}); }; + + window.test_driver_internal.get_platform_accessibility_node = function(dom_id, url) { + return create_action("get_platform_accessibility_node", {dom_id, url}); + }; })(); diff --git a/tools/wptrunner/wptrunner/testrunner.py b/tools/wptrunner/wptrunner/testrunner.py index 93e19fa47ba036..af99042bf1b6f5 100644 --- a/tools/wptrunner/wptrunner/testrunner.py +++ b/tools/wptrunner/wptrunner/testrunner.py @@ -313,7 +313,7 @@ def __init__(self, suite_name, index, test_queue, test_implementations, stop_flag, retry_index=0, rerun=1, pause_after_test=False, pause_on_unexpected=False, restart_on_unexpected=True, debug_info=None, - capture_stdio=True, restart_on_new_group=True, recording=None, max_restarts=5): + capture_stdio=True, restart_on_new_group=True, recording=None, max_restarts=5, product_name=None): """Thread that owns a single TestRunner process and any processes required by the TestRunner (e.g. the Firefox binary). @@ -332,6 +332,7 @@ def __init__(self, suite_name, index, test_queue, self.suite_name = suite_name self.manager_number = index self.test_implementation_key = None + self.product_name = product_name self.test_implementations = {} for key, test_implementation in test_implementations.items(): @@ -594,6 +595,7 @@ def start_test_runner(self): self.executor_kwargs["group_metadata"] = self.state.group_metadata self.executor_kwargs["browser_settings"] = self.browser.browser_settings executor_browser_cls, executor_browser_kwargs = self.browser.browser.executor_browser() + executor_browser_kwargs["product_name"] = self.product_name args = (self.remote_queue, self.command_queue, @@ -984,8 +986,10 @@ def __init__(self, suite_name, test_queue_builder, test_implementations, capture_stdio=True, restart_on_new_group=True, recording=None, - max_restarts=5): + max_restarts=5, + product_name=None): self.suite_name = suite_name + self.product_name = product_name self.test_queue_builder = test_queue_builder self.test_implementations = test_implementations self.pause_after_test = pause_after_test @@ -1031,7 +1035,8 @@ def run(self, tests): self.capture_stdio, self.restart_on_new_group, recording=self.recording, - max_restarts=self.max_restarts) + max_restarts=self.max_restarts, + product_name=self.product_name) manager.start() self.pool.add(manager) self.wait() diff --git a/tools/wptrunner/wptrunner/wptcommandline.py b/tools/wptrunner/wptrunner/wptcommandline.py index 87f51d6be7f49c..db9dca8b45e671 100644 --- a/tools/wptrunner/wptrunner/wptcommandline.py +++ b/tools/wptrunner/wptrunner/wptcommandline.py @@ -377,6 +377,8 @@ def create_parser(product_choices=None): chrome_group.add_argument("--no-enable-experimental", action="store_false", dest="enable_experimental", help="Do not enable --enable-experimental-web-platform-features flag " "on experimental channels") + chrome_group.add_argument( "--force-renderer-accessibility", action="store_true", + dest="force_renderer_accessibility",help="Turn on accessibility.") chrome_group.add_argument( "--enable-sanitizer", action="store_true", diff --git a/tools/wptrunner/wptrunner/wptrunner.py b/tools/wptrunner/wptrunner/wptrunner.py index d9d85de6a4d04b..9a45b72df328fb 100644 --- a/tools/wptrunner/wptrunner/wptrunner.py +++ b/tools/wptrunner/wptrunner/wptrunner.py @@ -307,6 +307,7 @@ def run_test_iteration(test_status, test_loader, test_queue_builder, kwargs["restart_on_new_group"], recording=recording, max_restarts=kwargs["max_restarts"], + product_name=product.name ) as manager_group: try: handle_interrupt_signals()