diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-15 03:34:42 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-15 03:34:42 +0000 |
commit | da4c7e7ed675c3bf405668739c3012d140856109 (patch) | |
tree | cdd868dba063fecba609a1d819de271f0d51b23e /accessible/tests/browser/windows/a11y_setup.py | |
parent | Adding upstream version 125.0.3. (diff) | |
download | firefox-da4c7e7ed675c3bf405668739c3012d140856109.tar.xz firefox-da4c7e7ed675c3bf405668739c3012d140856109.zip |
Adding upstream version 126.0.upstream/126.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'accessible/tests/browser/windows/a11y_setup.py')
-rw-r--r-- | accessible/tests/browser/windows/a11y_setup.py | 154 |
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) |