summaryrefslogtreecommitdiffstats
path: root/accessible/tests/browser/windows/a11y_setup.py
diff options
context:
space:
mode:
Diffstat (limited to 'accessible/tests/browser/windows/a11y_setup.py')
-rw-r--r--accessible/tests/browser/windows/a11y_setup.py154
1 files changed, 144 insertions, 10 deletions
diff --git a/accessible/tests/browser/windows/a11y_setup.py b/accessible/tests/browser/windows/a11y_setup.py
index 726eea07a4..860364d99b 100644
--- a/accessible/tests/browser/windows/a11y_setup.py
+++ b/accessible/tests/browser/windows/a11y_setup.py
@@ -61,9 +61,6 @@ uiaClient = comtypes.CoCreateInstance(
interface=uiaMod.IUIAutomation,
clsctx=comtypes.CLSCTX_INPROC_SERVER,
)
-TreeScope_Descendants = uiaMod.TreeScope_Descendants
-UIA_AutomationIdPropertyId = uiaMod.UIA_AutomationIdPropertyId
-del uiaMod
def AccessibleObjectFromWindow(hwnd, objectID=OBJID_CLIENT):
@@ -173,7 +170,7 @@ class WaitForWinEvent:
"""
def __init__(self, eventId, match):
- """event is the event id to wait for.
+ """eventId is the event id to wait for.
match is either None to match any object, an str containing the DOM id
of the desired object, or a function taking a WinEvent which should
return True if this is the requested event.
@@ -235,6 +232,7 @@ class WaitForWinEvent:
raise
finally:
user32.UnhookWinEvent(self._hook)
+ ctypes.windll.kernel32.CloseHandle(self._signal)
self._proc = None
if isinstance(self._matched, Exception):
raise self._matched from self._matched
@@ -243,17 +241,153 @@ class WaitForWinEvent:
def getDocUia():
"""Get the IUIAutomationElement for the document being tested."""
- # We start with IAccessible2 because there's no efficient way to
- # find the document we want with UIA.
- ia2 = getDocIa2()
- return uiaClient.ElementFromIAccessible(ia2, CHILDID_SELF)
+ # There's no efficient way to find the document we want with UIA. We can't
+ # get the IA2 and then get UIA from that because that will always use the
+ # IA2 -> UIA proxy, but we don't want that if we're trying to test our
+ # native implementation. For now, we just search the tree. In future, we
+ # could perhaps implement a custom property.
+ hwnd = getFirefoxHwnd()
+ root = uiaClient.ElementFromHandle(hwnd)
+ doc = findUiaByDomId(root, "body")
+ if not doc:
+ # Sometimes, when UIA is disabled, we can't find the document for some
+ # unknown reason. Since this only happens when UIA is disabled, we want
+ # the IA2 -> UIA proxy anyway, so we can start with IA2 in this case.
+ info("getUiaDoc: Falling back to IA2") # noqa: F821
+ ia2 = getDocIa2()
+ return uiaClient.ElementFromIAccessible(ia2, CHILDID_SELF)
+ child = uiaClient.RawViewWalker.GetFirstChildElement(doc)
+ if child and child.CurrentAutomationId == "default-iframe-id":
+ # This is an iframe or remoteIframe test.
+ doc = uiaClient.RawViewWalker.GetFirstChildElement(child)
+ return doc
def findUiaByDomId(root, id):
- cond = uiaClient.CreatePropertyCondition(UIA_AutomationIdPropertyId, id)
+ cond = uiaClient.CreatePropertyCondition(uiaMod.UIA_AutomationIdPropertyId, id)
# FindFirst ignores elements in the raw tree, so we have to use
# FindFirstBuildCache to override that, even though we don't want to cache
# anything.
request = uiaClient.CreateCacheRequest()
request.TreeFilter = uiaClient.RawViewCondition
- return root.FindFirstBuildCache(TreeScope_Descendants, cond, request)
+ el = root.FindFirstBuildCache(uiaMod.TreeScope_Descendants, cond, request)
+ if not el:
+ return None
+ # We need to test things that were introduced after UIA was initially
+ # introduced in Windows 7.
+ return el.QueryInterface(uiaMod.IUIAutomationElement9)
+
+
+class WaitForUiaEvent(comtypes.COMObject):
+ """Wait for a UIA event.
+ This should be used as follows:
+ 1. Create an instance to wait for the desired event.
+ 2. Perform the action that should fire the event.
+ 3. Call wait() on the instance you created in 1) to wait for the event.
+ """
+
+ # This tells comtypes which COM interfaces we implement. It will then call
+ # either `ISomeInterface_SomeMethod` or just `SomeMethod` on this instance
+ # when that method is called using COM. We use the shorter convention, since
+ # we don't anticipate method name conflicts with UIA interfaces.
+ _com_interfaces_ = [
+ uiaMod.IUIAutomationFocusChangedEventHandler,
+ uiaMod.IUIAutomationPropertyChangedEventHandler,
+ uiaMod.IUIAutomationEventHandler,
+ ]
+
+ def __init__(self, *, eventId=None, property=None, match=None):
+ """eventId is the event id to wait for. Alternatively, you can pass
+ property to wait for a particular property to change.
+ match is either None to match any object, an str containing the DOM id
+ of the desired object, or a function taking a IUIAutomationElement which
+ should return True if this is the requested event.
+ """
+ self._match = match
+ self._matched = None
+ # A kernel event used to signal when we get the desired event.
+ self._signal = ctypes.windll.kernel32.CreateEventW(None, True, False, None)
+ if eventId == uiaMod.UIA_AutomationFocusChangedEventId:
+ uiaClient.AddFocusChangedEventHandler(None, self)
+ elif eventId:
+ # Generic automation event.
+ uiaClient.AddAutomationEventHandler(
+ eventId,
+ uiaClient.GetRootElement(),
+ uiaMod.TreeScope_Subtree,
+ None,
+ self,
+ )
+ elif property:
+ uiaClient.AddPropertyChangedEventHandler(
+ uiaClient.GetRootElement(),
+ uiaMod.TreeScope_Subtree,
+ None,
+ self,
+ [property],
+ )
+ else:
+ raise ValueError("No supported event specified")
+
+ def _checkMatch(self, sender):
+ if isinstance(self._match, str):
+ try:
+ if sender.CurrentAutomationId == self._match:
+ self._matched = sender
+ except comtypes.COMError:
+ pass
+ elif callable(self._match):
+ try:
+ if self._match(sender):
+ self._matched = sender
+ except Exception as e:
+ self._matched = e
+ else:
+ self._matched = sender
+ if self._matched:
+ ctypes.windll.kernel32.SetEvent(self._signal)
+
+ def HandleFocusChangedEvent(self, sender):
+ self._checkMatch(sender)
+
+ def HandlePropertyChangedEvent(self, sender, propertyId, newValue):
+ self._checkMatch(sender)
+
+ def HandleAutomationEvent(self, sender, eventId):
+ self._checkMatch(sender)
+
+ def wait(self):
+ """Wait for and return the IUIAutomationElement which sent the desired
+ event."""
+ # Pump Windows messages until we get the desired event, which will be
+ # signalled using a kernel event.
+ handles = (ctypes.c_void_p * 1)(self._signal)
+ index = ctypes.wintypes.DWORD()
+ TIMEOUT = 10000
+ try:
+ ctypes.oledll.ole32.CoWaitForMultipleHandles(
+ COWAIT_DEFAULT, TIMEOUT, 1, handles, ctypes.byref(index)
+ )
+ except WindowsError as e:
+ if e.winerror == RPC_S_CALLPENDING:
+ raise TimeoutError("Timeout before desired event received")
+ raise
+ finally:
+ uiaClient.RemoveAllEventHandlers()
+ ctypes.windll.kernel32.CloseHandle(self._signal)
+ if isinstance(self._matched, Exception):
+ raise self._matched from self._matched
+ return self._matched
+
+
+def getUiaPattern(element, patternName):
+ """Get a control pattern interface from an IUIAutomationElement."""
+ patternId = getattr(uiaMod, f"UIA_{patternName}PatternId")
+ unknown = element.GetCurrentPattern(patternId)
+ if not unknown:
+ return None
+ # GetCurrentPattern returns an IUnknown. We have to QI to the real
+ # interface.
+ # Get the comtypes interface object.
+ interface = getattr(uiaMod, f"IUIAutomation{patternName}Pattern")
+ return unknown.QueryInterface(interface)