From 40a355a42d4a9444dc753c04c6608dade2f06a23 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 03:13:27 +0200 Subject: Adding upstream version 125.0.1. Signed-off-by: Daniel Baumann --- accessible/tests/browser/windows/a11y_setup.py | 126 ++++++++++++++++++++- accessible/tests/browser/windows/ia2/browser.toml | 2 + .../tests/browser/windows/ia2/browser_osPicker.js | 51 +++++++++ .../tests/browser/windows/ia2/browser_role.js | 2 +- accessible/tests/browser/windows/uia/browser.toml | 2 + .../browser/windows/uia/browser_controlType.js | 4 +- .../windows/uia/browser_elementFromPoint.js | 4 +- .../tests/browser/windows/uia/browser_tree.js | 104 +++++++++++++++++ accessible/tests/browser/windows/uia/head.js | 37 ++++++ 9 files changed, 321 insertions(+), 11 deletions(-) create mode 100644 accessible/tests/browser/windows/ia2/browser_osPicker.js create mode 100644 accessible/tests/browser/windows/uia/browser_tree.js (limited to 'accessible/tests/browser/windows') diff --git a/accessible/tests/browser/windows/a11y_setup.py b/accessible/tests/browser/windows/a11y_setup.py index d6dc19f0fb..726eea07a4 100644 --- a/accessible/tests/browser/windows/a11y_setup.py +++ b/accessible/tests/browser/windows/a11y_setup.py @@ -9,19 +9,28 @@ import ctypes import os from ctypes import POINTER, byref from ctypes.wintypes import BOOL, HWND, LPARAM, POINT # noqa: F401 +from dataclasses import dataclass +import comtypes.automation import comtypes.client import psutil from comtypes import COMError, IServiceProvider +CHILDID_SELF = 0 +COWAIT_DEFAULT = 0 +EVENT_OBJECT_FOCUS = 0x8005 +GA_ROOT = 2 +NAVRELATION_EMBEDS = 0x1009 +OBJID_CLIENT = -4 +RPC_S_CALLPENDING = -2147417835 +WINEVENT_OUTOFCONTEXT = 0 +WM_CLOSE = 0x0010 + user32 = ctypes.windll.user32 oleacc = ctypes.oledll.oleacc oleaccMod = comtypes.client.GetModule("oleacc.dll") IAccessible = oleaccMod.IAccessible del oleaccMod -OBJID_CLIENT = -4 -CHILDID_SELF = 0 -NAVRELATION_EMBEDS = 0x1009 # This is the path if running locally. ia2Tlb = os.path.join( os.getcwd(), @@ -65,6 +74,13 @@ def AccessibleObjectFromWindow(hwnd, objectID=OBJID_CLIENT): return p +def getWindowClass(hwnd): + MAX_CHARS = 257 + buffer = ctypes.create_unicode_buffer(MAX_CHARS) + user32.GetClassNameW(hwnd, buffer, MAX_CHARS) + return buffer.value + + def getFirefoxHwnd(): """Search all top level windows for the Firefox instance being tested. @@ -78,9 +94,7 @@ def getFirefoxHwnd(): @ctypes.WINFUNCTYPE(BOOL, HWND, LPARAM) def callback(hwnd, lParam): - name = ctypes.create_unicode_buffer(100) - user32.GetClassNameW(hwnd, name, 100) - if name.value != "MozillaWindowClass": + if getWindowClass(hwnd) != "MozillaWindowClass": return True pid = ctypes.wintypes.DWORD() user32.GetWindowThreadProcessId(hwnd, byref(pid)) @@ -127,6 +141,106 @@ def findIa2ByDomId(root, id): return descendant +@dataclass +class WinEvent: + event: int + hwnd: int + objectId: int + childId: int + + def getIa2(self): + acc = ctypes.POINTER(IAccessible)() + child = comtypes.automation.VARIANT() + ctypes.oledll.oleacc.AccessibleObjectFromEvent( + self.hwnd, + self.objectId, + self.childId, + ctypes.byref(acc), + ctypes.byref(child), + ) + if child.value != CHILDID_SELF: + # This isn't an IAccessible2 object. + return None + return toIa2(acc) + + +class WaitForWinEvent: + """Wait for a win event, usually for IAccessible2. + 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. + """ + + def __init__(self, eventId, match): + """event 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. + """ + 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) + + # We define this as a nested function because it has to be a static + # function, but we need a reference to self. + @ctypes.WINFUNCTYPE( + None, + ctypes.wintypes.HANDLE, + ctypes.wintypes.DWORD, + ctypes.wintypes.HWND, + ctypes.wintypes.LONG, + ctypes.wintypes.LONG, + ctypes.wintypes.DWORD, + ctypes.wintypes.DWORD, + ) + def winEventProc(hook, eventId, hwnd, objectId, childId, thread, time): + event = WinEvent(eventId, hwnd, objectId, childId) + if isinstance(match, str): + try: + ia2 = event.getIa2() + if f"id:{match};" in ia2.attributes: + self._matched = event + except (comtypes.COMError, TypeError): + pass + elif callable(match): + try: + if match(event): + self._matched = event + except Exception as e: + self._matched = e + if self._matched: + ctypes.windll.kernel32.SetEvent(self._signal) + + self._hook = user32.SetWinEventHook( + eventId, eventId, None, winEventProc, 0, 0, WINEVENT_OUTOFCONTEXT + ) + # Hold a reference to winEventProc so it doesn't get destroyed. + self._proc = winEventProc + + def wait(self): + """Wait for and return the desired WinEvent.""" + # 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: + user32.UnhookWinEvent(self._hook) + self._proc = None + if isinstance(self._matched, Exception): + raise self._matched from self._matched + return self._matched + + def getDocUia(): """Get the IUIAutomationElement for the document being tested.""" # We start with IAccessible2 because there's no efficient way to diff --git a/accessible/tests/browser/windows/ia2/browser.toml b/accessible/tests/browser/windows/ia2/browser.toml index d72b5f8a2d..d6226b73cc 100644 --- a/accessible/tests/browser/windows/ia2/browser.toml +++ b/accessible/tests/browser/windows/ia2/browser.toml @@ -6,4 +6,6 @@ skip-if = [ ] support-files = ["head.js"] +["browser_osPicker.js"] + ["browser_role.js"] diff --git a/accessible/tests/browser/windows/ia2/browser_osPicker.js b/accessible/tests/browser/windows/ia2/browser_osPicker.js new file mode 100644 index 0000000000..b14f2d0a5f --- /dev/null +++ b/accessible/tests/browser/windows/ia2/browser_osPicker.js @@ -0,0 +1,51 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +addAccessibleTask( + ``, + async function (browser, docAcc) { + info("Focusing file input"); + await runPython(` + global focused + focused = WaitForWinEvent(EVENT_OBJECT_FOCUS, "file") + `); + const file = findAccessibleChildByID(docAcc, "file"); + file.takeFocus(); + await runPython(` + focused.wait() + `); + ok(true, "file input got focus"); + info("Opening file picker"); + await runPython(` + global focused + focused = WaitForWinEvent( + EVENT_OBJECT_FOCUS, + lambda evt: getWindowClass(evt.hwnd) == "Edit" + ) + `); + file.doAction(0); + await runPython(` + global event + event = focused.wait() + `); + ok(true, "Picker got focus"); + info("Dismissing picker"); + await runPython(` + # If the picker is dismissed too quickly, it seems to re-enable the root + # window before we do. This sleep isn't ideal, but it's more likely to + # reproduce the case that our root window gets focus before it is enabled. + # See bug 1883568 for further details. + import time + time.sleep(1) + focused = WaitForWinEvent(EVENT_OBJECT_FOCUS, "file") + # Sending key presses to the picker is unreliable, so use WM_CLOSE. + pickerRoot = user32.GetAncestor(event.hwnd, GA_ROOT) + user32.SendMessageW(pickerRoot, WM_CLOSE, 0, 0) + focused.wait() + `); + ok(true, "file input got focus"); + } +); diff --git a/accessible/tests/browser/windows/ia2/browser_role.js b/accessible/tests/browser/windows/ia2/browser_role.js index 08e44c280f..89b560ab49 100644 --- a/accessible/tests/browser/windows/ia2/browser_role.js +++ b/accessible/tests/browser/windows/ia2/browser_role.js @@ -12,7 +12,7 @@ addAccessibleTask( `

p

`, - async function (browser, docAcc) { + async function () { let role = await runPython(` global doc doc = getDocIa2() diff --git a/accessible/tests/browser/windows/uia/browser.toml b/accessible/tests/browser/windows/uia/browser.toml index f7974d69c7..d1513c1822 100644 --- a/accessible/tests/browser/windows/uia/browser.toml +++ b/accessible/tests/browser/windows/uia/browser.toml @@ -9,3 +9,5 @@ support-files = ["head.js"] ["browser_controlType.js"] ["browser_elementFromPoint.js"] + +["browser_tree.js"] diff --git a/accessible/tests/browser/windows/uia/browser_controlType.js b/accessible/tests/browser/windows/uia/browser_controlType.js index 16db892581..3bb994f437 100644 --- a/accessible/tests/browser/windows/uia/browser_controlType.js +++ b/accessible/tests/browser/windows/uia/browser_controlType.js @@ -9,11 +9,11 @@ const UIA_ButtonControlTypeId = 50000; const UIA_DocumentControlTypeId = 50030; /* eslint-enable camelcase */ -addAccessibleTask( +addUiaTask( ` `, - async function (browser, docAcc) { + async function () { let controlType = await runPython(` global doc doc = getDocUia() diff --git a/accessible/tests/browser/windows/uia/browser_elementFromPoint.js b/accessible/tests/browser/windows/uia/browser_elementFromPoint.js index e2fda4ab30..acf6fe91c7 100644 --- a/accessible/tests/browser/windows/uia/browser_elementFromPoint.js +++ b/accessible/tests/browser/windows/uia/browser_elementFromPoint.js @@ -4,12 +4,12 @@ "use strict"; -addAccessibleTask( +addUiaTask( `