Skip to content

Commit 0598573

Browse files
authored
fix: Panic if the subclassing adapter is double-instantiated (#596)
1 parent 1d9b74c commit 0598573

File tree

6 files changed

+142
-11
lines changed

6 files changed

+142
-11
lines changed

Cargo.lock

Lines changed: 45 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

platforms/macos/src/subclass.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,13 @@ impl SubclassingAdapter {
146146
action_handler: impl 'static + ActionHandler,
147147
) -> Self {
148148
let view = Id::as_ptr(&retained_view) as *mut NSView;
149+
if !unsafe {
150+
objc_getAssociatedObject(view as *const NSView as *const _, associated_object_key())
151+
}
152+
.is_null()
153+
{
154+
panic!("subclassing adapter already instantiated on view {view:?}");
155+
}
149156
let adapter = unsafe { Adapter::new(view as *mut c_void, false, action_handler) };
150157
// Cast to a pointer and back to force the lifetime to 'static
151158
// SAFETY: We know the class will live as long as the instance,

platforms/windows/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,5 +38,6 @@ features = [
3838

3939
[dev-dependencies]
4040
once_cell = "1.13.0"
41+
parking_lot = "0.12.4"
4142
scopeguard = "1.1.0"
4243
winit = "0.30"

platforms/windows/src/subclass.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,12 @@ impl SubclassImpl {
9393
}
9494

9595
fn install(&mut self) {
96+
if !unsafe { GetPropW(self.hwnd, PROP_NAME) }.0.is_null() {
97+
panic!(
98+
"subclassing adapter already instantiated on window {:?}",
99+
self.hwnd.0
100+
);
101+
}
96102
unsafe {
97103
SetPropW(
98104
self.hwnd,

platforms/windows/src/tests/mod.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,9 @@ impl Scope {
180180
}
181181

182182
// It's not safe to run these UI-related tests concurrently.
183-
pub(crate) static MUTEX: Mutex<()> = Mutex::new(());
183+
// We need a non-poisoning mutex here because the subclassing adapter's
184+
// double-instantiation test intentionally panics.
185+
pub(crate) static MUTEX: parking_lot::Mutex<()> = parking_lot::const_mutex(());
184186

185187
pub(crate) fn scope<F>(
186188
window_title: &str,
@@ -191,7 +193,7 @@ pub(crate) fn scope<F>(
191193
where
192194
F: FnOnce(&Scope) -> Result<()>,
193195
{
194-
let _lock_guard = MUTEX.lock().unwrap();
196+
let _lock_guard = MUTEX.lock();
195197

196198
let window_mutex: Mutex<Option<WindowHandle>> = Mutex::new(None);
197199
let window_cv = Condvar::new();

platforms/windows/src/tests/subclassed.rs

Lines changed: 79 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,15 @@
66
use accesskit::{
77
Action, ActionHandler, ActionRequest, ActivationHandler, Node, NodeId, Role, Tree, TreeUpdate,
88
};
9-
use windows::Win32::{Foundation::*, UI::Accessibility::*};
9+
use once_cell::sync::Lazy;
10+
use windows::{
11+
core::*,
12+
Win32::{
13+
Foundation::*,
14+
System::LibraryLoader::GetModuleHandleW,
15+
UI::{Accessibility::*, WindowsAndMessaging::*},
16+
},
17+
};
1018
use winit::{
1119
application::ApplicationHandler,
1220
event::WindowEvent,
@@ -63,7 +71,14 @@ impl ActivationHandler for SimpleActivationHandler {
6371
}
6472

6573
// This module uses winit for the purpose of testing with a real third-party
66-
// window implementation that we don't control.
74+
// window implementation that we don't control. However, only one test
75+
// can use winit, because winit only allows an event loop to be created
76+
// once per process. So we end up creating our own window anyway for the
77+
// double-instantiation test.
78+
//
79+
// Also, while these tests don't use the main test harness or show the window,
80+
// they still need to run with the main harness's mutex, to avoid disturbing
81+
// other tests, particularly the focus test.
6782

6883
struct TestApplication;
6984

@@ -92,12 +107,69 @@ impl ApplicationHandler<()> for TestApplication {
92107

93108
#[test]
94109
fn has_native_uia() {
95-
// This test is simple enough that we know it's fine to run entirely
96-
// on one thread, so we don't need a full multithreaded test harness.
97-
// Still, we must prevent this test from running concurrently with other
98-
// tests, especially the focus test.
99-
let _lock_guard = MUTEX.lock().unwrap();
110+
let _lock_guard = MUTEX.lock();
100111
let event_loop = EventLoop::builder().with_any_thread(true).build().unwrap();
101112
let mut state = TestApplication {};
102113
event_loop.run_app(&mut state).unwrap();
103114
}
115+
116+
extern "system" fn wndproc(window: HWND, message: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
117+
unsafe { DefWindowProcW(window, message, wparam, lparam) }
118+
}
119+
120+
static WINDOW_CLASS_ATOM: Lazy<u16> = Lazy::new(|| {
121+
let class_name = w!("AccessKitSubclassTest");
122+
123+
let wc = WNDCLASSW {
124+
hCursor: unsafe { LoadCursorW(None, IDC_ARROW) }.unwrap(),
125+
hInstance: unsafe { GetModuleHandleW(None) }.unwrap().into(),
126+
lpszClassName: class_name,
127+
style: CS_HREDRAW | CS_VREDRAW,
128+
lpfnWndProc: Some(wndproc),
129+
..Default::default()
130+
};
131+
132+
let atom = unsafe { RegisterClassW(&wc) };
133+
if atom == 0 {
134+
panic!("{}", Error::from_win32());
135+
}
136+
atom
137+
});
138+
139+
fn create_window(title: &str) -> HWND {
140+
let module = HINSTANCE::from(unsafe { GetModuleHandleW(None).unwrap() });
141+
142+
let window = unsafe {
143+
CreateWindowExW(
144+
Default::default(),
145+
PCWSTR(*WINDOW_CLASS_ATOM as usize as _),
146+
&HSTRING::from(title),
147+
WS_OVERLAPPEDWINDOW,
148+
CW_USEDEFAULT,
149+
CW_USEDEFAULT,
150+
CW_USEDEFAULT,
151+
CW_USEDEFAULT,
152+
None,
153+
None,
154+
Some(module),
155+
None,
156+
)
157+
}
158+
.unwrap();
159+
if window.is_invalid() {
160+
panic!("{}", Error::from_win32());
161+
}
162+
163+
window
164+
}
165+
166+
#[test]
167+
#[should_panic(expected = "already instantiated")]
168+
fn double_instantiate() {
169+
let _lock_guard = MUTEX.lock();
170+
let window = create_window(WINDOW_TITLE);
171+
let _adapter1 =
172+
SubclassingAdapter::new(window, SimpleActivationHandler {}, NullActionHandler {});
173+
let _adapter2 =
174+
SubclassingAdapter::new(window, SimpleActivationHandler {}, NullActionHandler {});
175+
}

0 commit comments

Comments
 (0)