diff --git a/platforms/windows/examples/hello_world.rs b/platforms/windows/examples/hello_world.rs index 32920dbca..f2f68caea 100644 --- a/platforms/windows/examples/hello_world.rs +++ b/platforms/windows/examples/hello_world.rs @@ -9,7 +9,6 @@ use windows::{ Win32::{ Foundation::*, Graphics::Gdi::ValidateRect, - System::Com::*, System::LibraryLoader::GetModuleHandleW, UI::{Input::KeyboardAndMouse::*, WindowsAndMessaging::*}, }, @@ -229,18 +228,6 @@ fn create_window(title: &str, initial_state: TreeUpdate, initial_focus: NodeId) } fn main() -> Result<()> { - // There isn't a single correct answer to the question of how, or whether, - // to initialize COM. It's not clear whether this should even matter - // for a UI Automation provider that, like AccessKit, doesn't use - // COM threading. However, as discussed in #37, it apparently does matter, - // at least on some machines. If a program depends on legacy code - // that requires a single-threaded apartment (STA), then it should - // initialize COM that way. But that's not the case for AccessKit's - // examples and tests, and the multi-threaded apartment (MTA) - // is apparently more reliable for our use case, so we choose that. - unsafe { CoInitializeEx(std::ptr::null_mut(), COINIT_MULTITHREADED) }?; - let _com_guard = scopeguard::guard((), |_| unsafe { CoUninitialize() }); - let window = create_window(WINDOW_TITLE, get_initial_state(), INITIAL_FOCUS)?; unsafe { ShowWindow(window, SW_SHOW) }; diff --git a/platforms/windows/src/manager.rs b/platforms/windows/src/manager.rs index b26bcc75b..e2ccb303c 100644 --- a/platforms/windows/src/manager.rs +++ b/platforms/windows/src/manager.rs @@ -18,6 +18,14 @@ pub struct Manager { impl Manager { pub fn new(hwnd: HWND, initial_state: TreeUpdate) -> Self { + // It's unfortunate that we have to force UIA to initialize early; + // it would be more optimal to let UIA lazily initialize itself + // when we receive the first `WM_GETOBJECT`. But if we don't do this, + // then on a thread that's using a COM STA, we can get a race condition + // that leads to nested WM_GETOBJECT messages and, in some cases, + // ATs not realizing that our window natively implements UIA. See #37. + force_init_uia(); + Self { hwnd, tree: Tree::new(initial_state), @@ -58,3 +66,13 @@ impl Manager { unsafe { UiaReturnRawElementProvider(self.hwnd, wparam, lparam, el) } } } + +fn force_init_uia() { + // `UiaLookupId` is a cheap way of forcing UIA to initialize itself. + unsafe { + UiaLookupId( + AutomationIdentifierType_Property, + &ControlType_Property_GUID, + ) + }; +} diff --git a/platforms/windows/src/tests/mod.rs b/platforms/windows/src/tests/mod.rs index 28d178800..b0f6a6ff8 100644 --- a/platforms/windows/src/tests/mod.rs +++ b/platforms/windows/src/tests/mod.rs @@ -191,19 +191,17 @@ where { let _lock_guard = MUTEX.lock(); - unsafe { CoInitializeEx(std::ptr::null_mut(), COINIT_MULTITHREADED) }.unwrap(); - let _com_guard = scopeguard::guard((), |_| unsafe { CoUninitialize() }); - - let uia: IUIAutomation = - unsafe { CoCreateInstance(&CUIAutomation8, None, CLSCTX_INPROC_SERVER) }?; - let window_mutex: Mutex> = Mutex::new(None); let window_cv = Condvar::new(); crossbeam_utils::thread::scope(|thread_scope| { thread_scope.spawn(|_| { - unsafe { CoInitializeEx(std::ptr::null_mut(), COINIT_MULTITHREADED) }.unwrap(); - let _com_guard = scopeguard::guard((), |_| unsafe { CoUninitialize() }); + // 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, initial_state, initial_focus).unwrap(); @@ -232,6 +230,24 @@ where unsafe { PostMessageW(window, 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(std::ptr::null_mut(), 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) })