Vendor dependencies for 0.3.0 release

This commit is contained in:
2025-09-27 10:29:08 -05:00
parent 0c8d39d483
commit 82ab7f317b
26803 changed files with 16134934 additions and 0 deletions

View File

@@ -0,0 +1,336 @@
// Copyright 2022 The AccessKit Authors. All rights reserved.
// Licensed under the Apache License, Version 2.0 (found in
// the LICENSE-APACHE file) or the MIT license (found in
// the LICENSE-MIT file), at your option.
use accesskit::{ActionHandler, ActivationHandler};
use once_cell::sync::Lazy;
use std::{
cell::RefCell,
sync::{Arc, Condvar, Mutex},
thread,
time::Duration,
};
use windows as Windows;
use windows::{
core::*,
Win32::{
Foundation::*,
Graphics::Gdi::ValidateRect,
System::{Com::*, LibraryLoader::GetModuleHandleW},
UI::{Accessibility::*, WindowsAndMessaging::*},
},
};
use crate::window_handle::WindowHandle;
use super::{
context::{ActionHandlerNoMut, ActionHandlerWrapper},
Adapter,
};
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(5);
static WINDOW_CLASS_ATOM: Lazy<u16> = Lazy::new(|| {
let class_name = w!("AccessKitTest");
let wc = WNDCLASSW {
hCursor: unsafe { LoadCursorW(None, IDC_ARROW) }.unwrap(),
hInstance: unsafe { GetModuleHandleW(None) }.unwrap().into(),
lpszClassName: class_name,
style: CS_HREDRAW | CS_VREDRAW,
lpfnWndProc: Some(wndproc),
..Default::default()
};
let atom = unsafe { RegisterClassW(&wc) };
if atom == 0 {
panic!("{}", Error::from_win32());
}
atom
});
struct WindowState {
activation_handler: RefCell<Box<dyn ActivationHandler>>,
adapter: RefCell<Adapter>,
}
unsafe fn get_window_state(window: HWND) -> *const WindowState {
GetWindowLongPtrW(window, GWLP_USERDATA) as _
}
fn update_window_focus_state(window: HWND, is_focused: bool) {
let state = unsafe { &*get_window_state(window) };
let mut adapter = state.adapter.borrow_mut();
if let Some(events) = adapter.update_window_focus_state(is_focused) {
events.raise();
}
}
struct WindowCreateParams {
activation_handler: Box<dyn ActivationHandler>,
action_handler: Arc<dyn ActionHandlerNoMut + Send + Sync>,
}
extern "system" fn wndproc(window: HWND, message: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
match message {
WM_NCCREATE => {
let create_struct: &CREATESTRUCTW = unsafe { &mut *(lparam.0 as *mut _) };
let create_params: Box<WindowCreateParams> =
unsafe { Box::from_raw(create_struct.lpCreateParams as _) };
let WindowCreateParams {
activation_handler,
action_handler,
} = *create_params;
let adapter = Adapter::with_wrapped_action_handler(window, false, action_handler);
let state = Box::new(WindowState {
activation_handler: RefCell::new(activation_handler),
adapter: RefCell::new(adapter),
});
unsafe { SetWindowLongPtrW(window, GWLP_USERDATA, Box::into_raw(state) as _) };
unsafe { DefWindowProcW(window, message, wparam, lparam) }
}
WM_PAINT => {
unsafe { ValidateRect(window, None) }.unwrap();
LRESULT(0)
}
WM_DESTROY => {
let ptr = unsafe { SetWindowLongPtrW(window, GWLP_USERDATA, 0) };
if ptr != 0 {
drop(unsafe { Box::<WindowState>::from_raw(ptr as _) });
}
unsafe { PostQuitMessage(0) };
LRESULT(0)
}
WM_GETOBJECT => {
let state_ptr = unsafe { get_window_state(window) };
if state_ptr.is_null() {
// We need to be prepared to gracefully handle WM_GETOBJECT
// while the window is being destroyed; this can happen if
// the thread is using a COM STA.
return unsafe { DefWindowProcW(window, message, wparam, lparam) };
}
let state = unsafe { &*state_ptr };
let mut adapter = state.adapter.borrow_mut();
let mut activation_handler = state.activation_handler.borrow_mut();
let result = adapter.handle_wm_getobject(wparam, lparam, &mut **activation_handler);
drop(activation_handler);
drop(adapter);
result.map_or_else(
|| unsafe { DefWindowProcW(window, message, wparam, lparam) },
|result| result.into(),
)
}
WM_SETFOCUS | WM_EXITMENULOOP | WM_EXITSIZEMOVE => {
update_window_focus_state(window, true);
LRESULT(0)
}
WM_KILLFOCUS | WM_ENTERMENULOOP | WM_ENTERSIZEMOVE => {
update_window_focus_state(window, false);
LRESULT(0)
}
_ => unsafe { DefWindowProcW(window, message, wparam, lparam) },
}
}
fn create_window(
title: &str,
activation_handler: impl 'static + ActivationHandler,
action_handler: impl 'static + ActionHandler + Send,
) -> Result<HWND> {
let create_params = Box::new(WindowCreateParams {
activation_handler: Box::new(activation_handler),
action_handler: Arc::new(ActionHandlerWrapper::new(action_handler)),
});
let window = unsafe {
CreateWindowExW(
Default::default(),
PCWSTR(*WINDOW_CLASS_ATOM as usize as _),
&HSTRING::from(title),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
None,
None,
GetModuleHandleW(None).unwrap(),
Some(Box::into_raw(create_params) as _),
)?
};
if window.is_invalid() {
return Err(Error::from_win32());
}
Ok(window)
}
pub(crate) struct Scope {
pub(crate) uia: IUIAutomation,
pub(crate) window: WindowHandle,
}
impl Scope {
pub(crate) fn show_and_focus_window(&self) {
let _ = unsafe { ShowWindow(self.window.0, SW_SHOW) };
let _ = unsafe { SetForegroundWindow(self.window.0) };
}
}
// It's not safe to run these UI-related tests concurrently.
pub(crate) static MUTEX: Mutex<()> = Mutex::new(());
pub(crate) fn scope<F>(
window_title: &str,
activation_handler: impl 'static + ActivationHandler + Send,
action_handler: impl 'static + ActionHandler + Send,
f: F,
) -> Result<()>
where
F: FnOnce(&Scope) -> Result<()>,
{
let _lock_guard = MUTEX.lock().unwrap();
let window_mutex: Mutex<Option<WindowHandle>> = Mutex::new(None);
let window_cv = Condvar::new();
thread::scope(|thread_scope| {
thread_scope.spawn(|| {
// We explicitly don't want to initialize COM on the provider thread,
// because we want to make sure that the provider side of UIA works
// even if COM is never initialized on the provider thread
// (as is the case if the window is never shown), or if COM is
// initialized after the window is shown (as is the case,
// at least on some Windows 10 machines, due to IME support).
let window = create_window(window_title, activation_handler, action_handler).unwrap();
{
let mut state = window_mutex.lock().unwrap();
*state = Some(window.into());
window_cv.notify_one();
}
let mut message = MSG::default();
while unsafe { GetMessageW(&mut message, HWND::default(), 0, 0) }.into() {
let _ = unsafe { TranslateMessage(&message) };
unsafe { DispatchMessageW(&message) };
}
});
let window = {
let state = window_mutex.lock().unwrap();
let mut state = if state.is_none() {
window_cv.wait(state).unwrap()
} else {
state
};
state.take().unwrap()
};
let _window_guard = scopeguard::guard((), |_| {
unsafe { PostMessageW(window.0, WM_CLOSE, WPARAM(0), LPARAM(0)) }.unwrap()
});
// We must initialize COM before creating the UIA client. The MTA option
// is cleaner by far, especially when we want to wait for a UIA event
// handler to be called, and there's no reason not to use it here.
// Note that we don't initialize COM this way on the provider thread,
// as explained above. It's also important that we let the provider
// thread do its forced initialization of UIA, in an environment
// where COM has not been initialized, before we create the UIA client,
// which also triggers UIA initialization, in a thread where COM
// _has_ been initialized. This way, we ensure that the provider side
// of UIA works even if it is set up in an environment where COM
// has not been initialized, and that this sequence of events
// doesn't prevent the UIA client from working.
unsafe { CoInitializeEx(None, COINIT_MULTITHREADED) }.unwrap();
let _com_guard = scopeguard::guard((), |_| unsafe { CoUninitialize() });
let uia: IUIAutomation =
unsafe { CoCreateInstance(&CUIAutomation8, None, CLSCTX_INPROC_SERVER) }?;
let s = Scope { uia, window };
f(&s)
})
}
/// This must only be used to wrap UIA elements returned by a UIA client
/// that was created in the MTA. Those are safe to send between threads.
struct SendableUiaElement(IUIAutomationElement);
unsafe impl Send for SendableUiaElement {}
pub(crate) struct ReceivedFocusEvent {
mutex: Mutex<Option<SendableUiaElement>>,
cv: Condvar,
}
impl ReceivedFocusEvent {
fn new() -> Arc<Self> {
Arc::new(Self {
mutex: Mutex::new(None),
cv: Condvar::new(),
})
}
pub(crate) fn wait<F>(&self, f: F) -> IUIAutomationElement
where
F: Fn(&IUIAutomationElement) -> bool,
{
let mut received = self.mutex.lock().unwrap();
loop {
if let Some(SendableUiaElement(element)) = received.take() {
if f(&element) {
return element;
}
}
let (lock, result) = self.cv.wait_timeout(received, DEFAULT_TIMEOUT).unwrap();
assert!(!result.timed_out());
received = lock;
}
}
fn put(&self, element: IUIAutomationElement) {
let mut received = self.mutex.lock().unwrap();
*received = Some(SendableUiaElement(element));
self.cv.notify_one();
}
}
#[implement(Windows::Win32::UI::Accessibility::IUIAutomationFocusChangedEventHandler)]
pub(crate) struct FocusEventHandler {
received: Arc<ReceivedFocusEvent>,
}
// Because we create a UIA client in the COM MTA, this event handler
// _will_ be called from a different thread, and possibly multiple threads
// at once.
static_assertions::assert_impl_all!(FocusEventHandler: Send, Sync);
impl FocusEventHandler {
#[allow(clippy::new_ret_no_self)] // it does return self, but wrapped
pub(crate) fn new() -> (
IUIAutomationFocusChangedEventHandler,
Arc<ReceivedFocusEvent>,
) {
let received = ReceivedFocusEvent::new();
(
Self {
received: Arc::clone(&received),
}
.into(),
received,
)
}
}
#[allow(non_snake_case)]
impl IUIAutomationFocusChangedEventHandler_Impl for FocusEventHandler_Impl {
fn HandleFocusChangedEvent(&self, sender: Option<&IUIAutomationElement>) -> Result<()> {
self.received.put(sender.unwrap().clone());
Ok(())
}
}
mod simple;
mod subclassed;

View File

@@ -0,0 +1,196 @@
// Copyright 2022 The AccessKit Authors. All rights reserved.
// Licensed under the Apache License, Version 2.0 (found in
// the LICENSE-APACHE file) or the MIT license (found in
// the LICENSE-MIT file), at your option.
use accesskit::{
Action, ActionHandler, ActionRequest, ActivationHandler, Node, NodeId, Role, Tree, TreeUpdate,
};
use windows::{core::*, Win32::UI::Accessibility::*};
use super::*;
const WINDOW_TITLE: &str = "Simple test";
const WINDOW_ID: NodeId = NodeId(0);
const BUTTON_1_ID: NodeId = NodeId(1);
const BUTTON_2_ID: NodeId = NodeId(2);
fn make_button(label: &str) -> Node {
let mut node = Node::new(Role::Button);
node.set_label(label);
node.add_action(Action::Focus);
node
}
fn get_initial_state() -> TreeUpdate {
let mut root = Node::new(Role::Window);
root.set_children(vec![BUTTON_1_ID, BUTTON_2_ID]);
let button_1 = make_button("Button 1");
let button_2 = make_button("Button 2");
TreeUpdate {
nodes: vec![
(WINDOW_ID, root),
(BUTTON_1_ID, button_1),
(BUTTON_2_ID, button_2),
],
tree: Some(Tree::new(WINDOW_ID)),
focus: BUTTON_1_ID,
}
}
pub struct NullActionHandler;
impl ActionHandler for NullActionHandler {
fn do_action(&mut self, _request: ActionRequest) {}
}
struct SimpleActivationHandler;
impl ActivationHandler for SimpleActivationHandler {
fn request_initial_tree(&mut self) -> Option<TreeUpdate> {
Some(get_initial_state())
}
}
fn scope<F>(f: F) -> Result<()>
where
F: FnOnce(&Scope) -> Result<()>,
{
super::scope(
WINDOW_TITLE,
SimpleActivationHandler {},
NullActionHandler {},
f,
)
}
#[test]
fn has_native_uia() -> Result<()> {
scope(|s| {
let has_native_uia: bool = unsafe { UiaHasServerSideProvider(s.window.0) }.into();
assert!(has_native_uia);
Ok(())
})
}
fn is_button_named(element: &IUIAutomationElement, expected_name: &str) -> bool {
let control_type = unsafe { element.CurrentControlType() }.unwrap();
let name = unsafe { element.CurrentName() }.unwrap();
let name: String = name.try_into().unwrap();
control_type == UIA_ButtonControlTypeId && name == expected_name
}
fn is_button_1(element: &IUIAutomationElement) -> bool {
is_button_named(element, "Button 1")
}
fn is_button_2(element: &IUIAutomationElement) -> bool {
is_button_named(element, "Button 2")
}
#[test]
fn navigation() -> Result<()> {
scope(|s| {
let root = unsafe { s.uia.ElementFromHandle(s.window.0) }?;
let walker = unsafe { s.uia.ControlViewWalker() }?;
// The children of the window include the children that we provide,
// but also the title bar provided by the OS. We know that our own
// children are in the order we specified, but we don't know
// their position relative to the title bar. In fact, Windows
// has changed this in the past.
//
// Note that a UIA client would normally use the UIA condition feature
// to traverse the tree looking for an element that meets
// some condition. But we want to be explicit about navigating
// forward (and backward below) through only the immediate children.
// We'll accept the performance hit of multiple cross-thread calls
// (insignificant in this case) to achieve that.
let mut button_1_forward: Option<IUIAutomationElement> = None;
let mut wrapped_child = unsafe { walker.GetFirstChildElement(&root) };
while let Ok(child) = wrapped_child {
if is_button_1(&child) {
button_1_forward = Some(child);
break;
}
wrapped_child = unsafe { walker.GetNextSiblingElement(&child) };
}
let button_1_forward = button_1_forward.unwrap();
let mut button_2_forward: Option<IUIAutomationElement> = None;
let wrapped_child = unsafe { walker.GetNextSiblingElement(&button_1_forward) };
if let Ok(child) = wrapped_child {
if is_button_2(&child) {
button_2_forward = Some(child);
}
}
let _button_2_forward = button_2_forward.unwrap();
let mut button_2_backward: Option<IUIAutomationElement> = None;
let mut wrapped_child = unsafe { walker.GetLastChildElement(&root) };
while let Ok(child) = wrapped_child {
if is_button_2(&child) {
button_2_backward = Some(child);
break;
}
wrapped_child = unsafe { walker.GetPreviousSiblingElement(&child) };
}
let button_2_backward = button_2_backward.unwrap();
let mut button_1_backward: Option<IUIAutomationElement> = None;
let wrapped_child = unsafe { walker.GetPreviousSiblingElement(&button_2_backward) };
if let Ok(child) = wrapped_child {
if is_button_1(&child) {
button_1_backward = Some(child);
}
}
let button_1_backward = button_1_backward.unwrap();
let equal: bool =
unsafe { s.uia.CompareElements(&button_1_forward, &button_1_backward) }?.into();
assert!(equal);
let parent = unsafe { walker.GetParentElement(&button_1_forward) }?;
let equal: bool = unsafe { s.uia.CompareElements(&parent, &root) }?.into();
assert!(equal);
let desktop_root = unsafe { s.uia.GetRootElement() }?;
let parent = unsafe { walker.GetParentElement(&root) }?;
let equal: bool = unsafe { s.uia.CompareElements(&parent, &desktop_root) }?.into();
assert!(equal);
let wrapped_child = unsafe { walker.GetFirstChildElement(&button_1_forward) };
assert_eq!(Err(Error::empty()), wrapped_child);
let wrapped_child = unsafe { walker.GetLastChildElement(&button_1_forward) };
assert_eq!(Err(Error::empty()), wrapped_child);
Ok(())
})
}
#[test]
fn focus() -> Result<()> {
scope(|s| {
let (focus_event_handler, received_focus_event) = FocusEventHandler::new();
unsafe {
s.uia
.AddFocusChangedEventHandler(None, &focus_event_handler)
}?;
s.show_and_focus_window();
let focus_from_event = received_focus_event.wait(is_button_1);
let has_focus: bool = unsafe { focus_from_event.CurrentHasKeyboardFocus() }?.into();
assert!(has_focus);
let is_focusable: bool = unsafe { focus_from_event.CurrentIsKeyboardFocusable() }?.into();
assert!(is_focusable);
let focus_on_demand = unsafe { s.uia.GetFocusedElement() }?;
let equal: bool =
unsafe { s.uia.CompareElements(&focus_from_event, &focus_on_demand) }?.into();
assert!(equal);
Ok(())
})
}

View File

@@ -0,0 +1,103 @@
// Copyright 2022 The AccessKit Authors. All rights reserved.
// Licensed under the Apache License, Version 2.0 (found in
// the LICENSE-APACHE file) or the MIT license (found in
// the LICENSE-MIT file), at your option.
use accesskit::{
Action, ActionHandler, ActionRequest, ActivationHandler, Node, NodeId, Role, Tree, TreeUpdate,
};
use windows::Win32::{Foundation::*, UI::Accessibility::*};
use winit::{
application::ApplicationHandler,
event::WindowEvent,
event_loop::{ActiveEventLoop, EventLoop},
platform::windows::EventLoopBuilderExtWindows,
raw_window_handle::{HasWindowHandle, RawWindowHandle},
window::{Window, WindowId},
};
use super::MUTEX;
use crate::SubclassingAdapter;
const WINDOW_TITLE: &str = "Simple test";
const WINDOW_ID: NodeId = NodeId(0);
const BUTTON_1_ID: NodeId = NodeId(1);
const BUTTON_2_ID: NodeId = NodeId(2);
fn make_button(label: &str) -> Node {
let mut node = Node::new(Role::Button);
node.set_label(label);
node.add_action(Action::Focus);
node
}
fn get_initial_state() -> TreeUpdate {
let mut root = Node::new(Role::Window);
root.set_children(vec![BUTTON_1_ID, BUTTON_2_ID]);
let button_1 = make_button("Button 1");
let button_2 = make_button("Button 2");
TreeUpdate {
nodes: vec![
(WINDOW_ID, root),
(BUTTON_1_ID, button_1),
(BUTTON_2_ID, button_2),
],
tree: Some(Tree::new(WINDOW_ID)),
focus: BUTTON_1_ID,
}
}
pub struct NullActionHandler;
impl ActionHandler for NullActionHandler {
fn do_action(&mut self, _request: ActionRequest) {}
}
struct SimpleActivationHandler;
impl ActivationHandler for SimpleActivationHandler {
fn request_initial_tree(&mut self) -> Option<TreeUpdate> {
Some(get_initial_state())
}
}
// This module uses winit for the purpose of testing with a real third-party
// window implementation that we don't control.
struct TestApplication;
impl ApplicationHandler<()> for TestApplication {
fn window_event(&mut self, _: &ActiveEventLoop, _: WindowId, _: WindowEvent) {}
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
let window_attributes = Window::default_attributes()
.with_title(WINDOW_TITLE)
.with_visible(false);
let window = event_loop.create_window(window_attributes).unwrap();
let hwnd = match window.window_handle().unwrap().as_raw() {
RawWindowHandle::Win32(handle) => HWND(handle.hwnd.get() as *mut core::ffi::c_void),
RawWindowHandle::WinRt(_) => unimplemented!(),
_ => unreachable!(),
};
let adapter =
SubclassingAdapter::new(hwnd, SimpleActivationHandler {}, NullActionHandler {});
assert!(unsafe { UiaHasServerSideProvider(hwnd) }.as_bool());
drop(window);
drop(adapter);
event_loop.exit();
}
}
#[test]
fn has_native_uia() {
// This test is simple enough that we know it's fine to run entirely
// on one thread, so we don't need a full multithreaded test harness.
// Still, we must prevent this test from running concurrently with other
// tests, especially the focus test.
let _lock_guard = MUTEX.lock().unwrap();
let event_loop = EventLoop::builder().with_any_thread(true).build().unwrap();
let mut state = TestApplication {};
event_loop.run_app(&mut state).unwrap();
}