Skip to content

Commit f162a04

Browse files
committed
fix: Expose dialogs in the platform adapters
1 parent 7a444c8 commit f162a04

File tree

4 files changed

+117
-9
lines changed

4 files changed

+117
-9
lines changed

platforms/atspi-common/src/node.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,11 @@ impl NodeWrapper<'_> {
284284
let state = self.0;
285285
let atspi_role = self.role();
286286
let mut atspi_state = StateSet::empty();
287-
if state.parent_id().is_none() && state.role() == Role::Window && is_window_focused {
287+
if is_window_focused
288+
&& ((state.parent_id().is_none() && state.role() == Role::Window)
289+
|| (state.is_dialog()
290+
&& state.tree_state.active_dialog().map(|d| d.id()) == Some(state.id())))
291+
{
288292
atspi_state.insert(State::Active);
289293
}
290294
if state.is_text_input() && !state.is_read_only() {
@@ -314,6 +318,9 @@ impl NodeWrapper<'_> {
314318
if atspi_role != AtspiRole::ToggleButton && state.toggled().is_some() {
315319
atspi_state.insert(State::Checkable);
316320
}
321+
if state.is_modal() {
322+
atspi_state.insert(State::Modal);
323+
}
317324
if let Some(selected) = state.is_selected() {
318325
if !state.is_disabled() {
319326
atspi_state.insert(State::Selectable);

platforms/windows/src/adapter.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,14 @@ impl TreeChangeHandler for AdapterChangeHandler<'_> {
214214
return;
215215
}
216216
let wrapper = NodeWrapper(node);
217+
if node.is_dialog() {
218+
let platform_node = PlatformNode::new(self.context, node.id());
219+
let element: IRawElementProviderSimple = platform_node.into();
220+
self.queue.push(QueuedEvent::Simple {
221+
element,
222+
event_id: UIA_Window_WindowOpenedEventId,
223+
});
224+
}
217225
if wrapper.name().is_some() && node.live() != Live::Off {
218226
let platform_node = PlatformNode::new(self.context, node.id());
219227
let element: IRawElementProviderSimple = platform_node.into();
@@ -234,6 +242,14 @@ impl TreeChangeHandler for AdapterChangeHandler<'_> {
234242
let old_node_was_filtered_out = filter(old_node) != FilterResult::Include;
235243
if filter(new_node) != FilterResult::Include {
236244
if !old_node_was_filtered_out {
245+
if old_node.is_dialog() {
246+
let platform_node = PlatformNode::new(self.context, old_node.id());
247+
let element: IRawElementProviderSimple = platform_node.into();
248+
self.queue.push(QueuedEvent::Simple {
249+
element,
250+
event_id: UIA_Window_WindowClosedEventId,
251+
});
252+
}
237253
let old_wrapper = NodeWrapper(old_node);
238254
if old_wrapper.is_selection_item_pattern_supported() && old_wrapper.is_selected() {
239255
self.handle_selection_state_change(old_node, false);
@@ -263,6 +279,14 @@ impl TreeChangeHandler for AdapterChangeHandler<'_> {
263279
event_id: UIA_LiveRegionChangedEventId,
264280
});
265281
}
282+
if old_node_was_filtered_out && new_node.is_dialog() {
283+
let platform_node = PlatformNode::new(self.context, new_node.id());
284+
let element: IRawElementProviderSimple = platform_node.into();
285+
self.queue.push(QueuedEvent::Simple {
286+
element,
287+
event_id: UIA_Window_WindowOpenedEventId,
288+
});
289+
}
266290
if new_wrapper.is_selection_item_pattern_supported()
267291
&& (new_wrapper.is_selected() != old_wrapper.is_selected()
268292
|| (old_node_was_filtered_out && new_wrapper.is_selected()))
@@ -282,6 +306,14 @@ impl TreeChangeHandler for AdapterChangeHandler<'_> {
282306
if filter(node) != FilterResult::Include {
283307
return;
284308
}
309+
if node.is_dialog() {
310+
let platform_node = PlatformNode::new(self.context, node.id());
311+
let element: IRawElementProviderSimple = platform_node.into();
312+
self.queue.push(QueuedEvent::Simple {
313+
element,
314+
event_id: UIA_Window_WindowClosedEventId,
315+
});
316+
}
285317
let wrapper = NodeWrapper(node);
286318
if wrapper.is_selection_item_pattern_supported() {
287319
self.handle_selection_state_change(node, false);

platforms/windows/src/node.rs

Lines changed: 73 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -96,11 +96,10 @@ impl NodeWrapper<'_> {
9696
Role::Abbr => UIA_TextControlTypeId,
9797
Role::Alert => UIA_TextControlTypeId,
9898
Role::AlertDialog => {
99-
// Chromium's implementation suggests the use of
100-
// UIA_TextControlTypeId, not UIA_PaneControlTypeId, because some
101-
// Windows screen readers are not compatible with
102-
// Role::AlertDialog yet.
103-
UIA_TextControlTypeId
99+
// Documentation suggests the use of UIA_PaneControlTypeId,
100+
// but Chromium's implementation uses UIA_WindowControlTypeId
101+
// instead.
102+
UIA_WindowControlTypeId
104103
}
105104
Role::Application => UIA_PaneControlTypeId,
106105
Role::Article => UIA_GroupControlTypeId,
@@ -123,7 +122,12 @@ impl NodeWrapper<'_> {
123122
Role::DescriptionListDetail => UIA_TextControlTypeId,
124123
Role::DescriptionListTerm => UIA_ListItemControlTypeId,
125124
Role::Details => UIA_GroupControlTypeId,
126-
Role::Dialog => UIA_PaneControlTypeId,
125+
Role::Dialog => {
126+
// Documentation suggests the use of UIA_PaneControlTypeId,
127+
// but Chromium's implementation uses UIA_WindowControlTypeId
128+
// instead.
129+
UIA_WindowControlTypeId
130+
}
127131
Role::Directory => UIA_ListControlTypeId,
128132
Role::DisclosureTriangle => UIA_ButtonControlTypeId,
129133
Role::Document | Role::Terminal => UIA_DocumentControlTypeId,
@@ -261,6 +265,17 @@ impl NodeWrapper<'_> {
261265
self.0.role_description()
262266
}
263267

268+
fn aria_role(&self) -> Option<&str> {
269+
match self.0.role() {
270+
Role::AlertDialog => Some("alertdialog"),
271+
Role::Dialog => Some("dialog"),
272+
_ => {
273+
// TODO: Expose more ARIA roles.
274+
None
275+
}
276+
}
277+
}
278+
264279
pub(crate) fn name(&self) -> Option<WideString> {
265280
let mut result = WideString::default();
266281
if self.0.label_comes_from_value() {
@@ -443,6 +458,18 @@ impl NodeWrapper<'_> {
443458
self.0.role() == Role::PasswordInput
444459
}
445460

461+
fn is_dialog(&self) -> bool {
462+
self.0.is_dialog()
463+
}
464+
465+
fn is_window_pattern_supported(&self) -> bool {
466+
self.0.is_dialog()
467+
}
468+
469+
fn is_modal(&self) -> bool {
470+
self.0.is_modal()
471+
}
472+
446473
pub(crate) fn enqueue_property_changes(
447474
&self,
448475
queue: &mut Vec<QueuedEvent>,
@@ -501,7 +528,8 @@ impl NodeWrapper<'_> {
501528
IRangeValueProvider,
502529
ISelectionItemProvider,
503530
ISelectionProvider,
504-
ITextProvider
531+
ITextProvider,
532+
IWindowProvider
505533
)]
506534
pub(crate) struct PlatformNode {
507535
pub(crate) context: Weak<Context>,
@@ -948,6 +976,7 @@ macro_rules! patterns {
948976
properties! {
949977
(UIA_ControlTypePropertyId, control_type),
950978
(UIA_LocalizedControlTypePropertyId, localized_control_type),
979+
(UIA_AriaRolePropertyId, aria_role),
951980
(UIA_NamePropertyId, name),
952981
(UIA_FullDescriptionPropertyId, description),
953982
(UIA_HelpTextPropertyId, placeholder),
@@ -963,7 +992,8 @@ properties! {
963992
(UIA_IsRequiredForFormPropertyId, is_required),
964993
(UIA_IsPasswordPropertyId, is_password),
965994
(UIA_PositionInSetPropertyId, position_in_set),
966-
(UIA_SizeOfSetPropertyId, size_of_set)
995+
(UIA_SizeOfSetPropertyId, size_of_set),
996+
(UIA_IsDialogPropertyId, is_dialog)
967997
}
968998

969999
patterns! {
@@ -1103,6 +1133,41 @@ patterns! {
11031133
}
11041134
})
11051135
}
1136+
)),
1137+
(UIA_WindowPatternId, IWindowProvider, IWindowProvider_Impl, is_window_pattern_supported, (
1138+
(UIA_WindowIsModalPropertyId, IsModal, is_modal, BOOL)
1139+
), (
1140+
fn SetVisualState(&self, _: WindowVisualState) -> Result<()> {
1141+
Err(invalid_operation())
1142+
},
1143+
1144+
fn Close(&self) -> Result<()> {
1145+
Err(not_supported())
1146+
},
1147+
1148+
fn WaitForInputIdle(&self, _: i32) -> Result<BOOL> {
1149+
Err(not_supported())
1150+
},
1151+
1152+
fn CanMaximize(&self) -> Result<BOOL> {
1153+
Err(not_supported())
1154+
},
1155+
1156+
fn CanMinimize(&self) -> Result<BOOL> {
1157+
Err(not_supported())
1158+
},
1159+
1160+
fn WindowVisualState(&self) -> Result<WindowVisualState> {
1161+
Err(not_supported())
1162+
},
1163+
1164+
fn WindowInteractionState(&self) -> Result<WindowInteractionState> {
1165+
Ok(WindowInteractionState_ReadyForUserInteraction)
1166+
},
1167+
1168+
fn IsTopmost(&self) -> Result<BOOL> {
1169+
Err(not_supported())
1170+
}
11061171
))
11071172
}
11081173

platforms/windows/src/util.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,10 @@ pub(crate) fn invalid_operation() -> Error {
237237
HRESULT(UIA_E_INVALIDOPERATION as _).into()
238238
}
239239

240+
pub(crate) fn not_supported() -> Error {
241+
HRESULT(UIA_E_NOTSUPPORTED as _).into()
242+
}
243+
240244
pub(crate) fn client_top_left(hwnd: WindowHandle) -> Point {
241245
let mut result = POINT::default();
242246
// If ClientToScreen fails, that means the window is gone.

0 commit comments

Comments
 (0)