Files
wine-access/tests/event_stall_fixture.cpp
deedra 0c69564780 Add regression fixtures for blocked Wine MSAA event providers
Add a MinGW fixture and isolated D-Bus runner that stall WM_GETOBJECT or
accessible text-property retrieval for eight seconds. While the provider
is blocked, a separate foreground editor must deliver repeated character
events and native button feedback without changing its text, caret, or
selection. Reject stale focus after the blocked provider returns and verify
normal WM_QUIT shutdown completes before a second provider stall ends.

Cover both 64-bit and 32-bit applications, ordinary property-getter stalls,
background focus events that must not query providers or retire foreground
feedback, and owned nonactivating accessible popups that preserve native
focus. Support the alternate Orca recording endpoint as well as Cthulhu.

Use disposable Wine prefixes and recording services so these checks do not
send speech or braille to the running reader. Compile fixtures with the
matching MinGW C++ compiler and clean up the test processes and prefix's
Wine server when each run finishes.

Validation, run sequentially on a private dummy Xorg display (:94):
- cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE=winelib-toolchain.cmake
- cmake --build build --parallel
- ctest --test-dir build --output-on-failure (4/4 passed)
- python3 tests/event_stall_integration.py --build-dir build, repeated with
  no extra option, --32-bit, --getter, --background, --popup, --reader orca
  (all six runs passed)
- python3 tests/menu_integration.py --build-dir build --fixture <fixture>,
  for menu, edit, controls, and list, each with and without --32-bit
  (all eight runs passed)
- git diff --check (passed)

Xvfb is not installed, so use the installed unprivileged Xorg binary and
dummy driver for isolation. These checks record D-Bus output and native
fixture state; installed-helper MUSHclient, physical input, audible speech,
and physical braille acceptance remain separate live checks.
2026-10-12 23:31:03 -07:00

122 lines
5.2 KiB
C++

// SPDX-License-Identifier: LGPL-2.1-or-later
#include <windows.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "../menu_bridge.h"
static BOOL editor_mode, stalled, getter_mode, background_mode, popup_mode;
static HWND editor, button, provider;
static WNDPROC native_provider_proc;
static unsigned step;
static const WCHAR *cls_name = L"WineEventPopupFixture";
static void marker(const WCHAR *variable) {
WCHAR path[4096];
if (!GetEnvironmentVariableW(variable, path, 4096)) return;
HANDLE file = CreateFileW(path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (file != INVALID_HANDLE_VALUE) CloseHandle(file);
}
static void stall_provider() {
if (stalled) return;
stalled = TRUE;
marker(L"WINE_ACCESS_STALL_READY");
Sleep(8000);
marker(L"WINE_ACCESS_STALL_DONE");
}
static LRESULT CALLBACK property_proc(HWND window, UINT message, WPARAM wp, LPARAM lp) {
if (message == WM_GETTEXT || message == WM_GETTEXTLENGTH) stall_provider();
return CallWindowProcW(native_provider_proc, window, message, wp, lp);
}
static LRESULT CALLBACK window_proc(HWND window, UINT message, WPARAM wp, LPARAM lp) {
if (message == WM_GETOBJECT && (LONG)lp == OBJID_CLIENT && !editor_mode && !stalled) {
if (getter_mode) return DefWindowProcW(window, message, wp, lp);
stall_provider();
}
if (message == WM_TIMER) {
if (!editor_mode) {
KillTimer(window, 1);
if (getter_mode) {
SetFocus(provider);
NotifyWinEvent(EVENT_OBJECT_FOCUS, provider, OBJID_CLIENT, CHILDID_SELF);
} else NotifyWinEvent(EVENT_OBJECT_FOCUS, window, OBJID_CLIENT, CHILDID_SELF);
} else if (++step == 1) {
SetForegroundWindow(window); SetFocus(editor);
PostMessageW(editor, WM_CHAR, 'a', 0);
PostMessageW(editor, WM_CHAR, 'a', 0);
} else {
KillTimer(window, 1);
WCHAR text[16]; DWORD start, end;
GetWindowTextW(editor, text, 16);
SendMessageW(editor, EM_GETSEL, (WPARAM)&start, (LPARAM)&end);
if (lstrcmpW(text, L"aa") || start != 2 || end != 2) ExitProcess(2);
marker(L"WINE_ACCESS_EDITOR_CHECKED");
SetFocus(button);
NotifyWinEvent(EVENT_OBJECT_FOCUS, button, OBJID_CLIENT, CHILDID_SELF);
if (popup_mode) {
HWND popup = CreateWindowExW(WS_EX_NOACTIVATE, cls_name, L"Owned popup provider",
WS_POPUP | WS_VISIBLE, 200, 200, 200, 80, window, NULL, GetModuleHandleW(NULL), NULL);
// A bare X server can activate a newly mapped Wine popup despite
// WS_EX_NOACTIVATE. Establish the intended owner focus explicitly
// before testing its independent accessible focus notification.
SetForegroundWindow(window); SetFocus(button);
NotifyWinEvent(EVENT_OBJECT_FOCUS, popup, OBJID_CLIENT, CHILDID_SELF);
if (GetForegroundWindow() != window || GetFocus() != button) {
fprintf(stderr, "popup focus changed: foreground=%p expected=%p focus=%p expected=%p\n",
GetForegroundWindow(), window, GetFocus(), button);
ExitProcess(4);
}
}
}
return 0;
}
if (message == WM_DESTROY) { PostQuitMessage(0); return 0; }
return DefWindowProcW(window, message, wp, lp);
}
int WINAPI WinMain(HINSTANCE instance, HINSTANCE previous, LPSTR args, int show) {
(void)previous; (void)show;
if (strstr(args, "quit-helper")) {
HWND bridge = FindWindowW(WINE_ACCESS_MENU_CLASS, NULL);
DWORD thread = GetWindowThreadProcessId(bridge, NULL);
return thread && PostThreadMessageW(thread, WM_QUIT, 0, 0) ? 0 : 3;
}
if (FAILED(CoInitializeEx(NULL, COINIT_APARTMENTTHREADED))) return 5;
editor_mode = strstr(args, "editor") != NULL;
getter_mode = strstr(args, "getter") != NULL;
background_mode = strstr(args, "background") != NULL;
popup_mode = strstr(args, "popup") != NULL;
WNDCLASSW cls = {};
cls.hInstance = instance; cls.lpfnWndProc = window_proc; cls.lpszClassName = L"WineEventStallFixture";
RegisterClassW(&cls);
HWND window = CreateWindowW(cls.lpszClassName,
editor_mode ? L"Responsive editor fixture" : L"Stalled provider fixture",
WS_OVERLAPPEDWINDOW, 100, 100, 500, 300, NULL, NULL, instance, NULL);
if (editor_mode) {
editor = CreateWindowW(L"Edit", L"", WS_CHILD | WS_VISIBLE | WS_TABSTOP,
10, 10, 300, 30, window, NULL, instance, NULL);
button = CreateWindowW(L"Button", L"Responsive button", WS_CHILD | WS_VISIBLE | WS_TABSTOP,
10, 60, 200, 30, window, NULL, instance, NULL);
}
if (getter_mode) {
provider = CreateWindowW(L"Static", L"Stalled property provider", WS_CHILD | WS_VISIBLE,
10, 10, 300, 30, window, NULL, instance, NULL);
native_provider_proc = reinterpret_cast<WNDPROC>(SetWindowLongPtrW(provider, GWLP_WNDPROC,
reinterpret_cast<LONG_PTR>(property_proc)));
}
WNDCLASSW popupClass = cls;
popupClass.lpszClassName = cls_name; popupClass.lpfnWndProc = DefWindowProcW;
RegisterClassW(&popupClass);
ShowWindow(window, background_mode ? SW_SHOWNOACTIVATE : SW_SHOW);
if (!background_mode) SetForegroundWindow(window);
if (editor_mode) SetFocus(editor);
SetTimer(window, 1, 500, NULL);
MSG message;
while (GetMessageW(&message, NULL, 0, 0) > 0) {
TranslateMessage(&message); DispatchMessageW(&message);
}
CoUninitialize();
return 0;
}