Skip to content

Commit 5ae557b

Browse files
authored
fix: Support text fields without a value property (#274)
1 parent d918278 commit 5ae557b

File tree

7 files changed

+108
-19
lines changed

7 files changed

+108
-19
lines changed

consumer/src/node.rs

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ impl<'a> Node<'a> {
5050
is_focused: self.is_focused(),
5151
is_root: self.is_root(),
5252
name: self.name(),
53+
value: self.value(),
5354
live: self.live(),
5455
supports_text_ranges: self.supports_text_ranges(),
5556
}
@@ -379,10 +380,6 @@ impl NodeState {
379380
self.data().checked_state()
380381
}
381382

382-
pub fn value(&self) -> Option<&str> {
383-
self.data().value()
384-
}
385-
386383
pub fn numeric_value(&self) -> Option<f64> {
387384
self.data().numeric_value()
388385
}
@@ -534,6 +531,20 @@ impl<'a> Node<'a> {
534531
(!names.is_empty()).then(move || names.join(" "))
535532
}
536533
}
534+
535+
pub fn value(&self) -> Option<String> {
536+
if let Some(value) = &self.data().value() {
537+
Some(value.to_string())
538+
} else if self.supports_text_ranges() && !self.is_multiline() {
539+
Some(self.document_range().text())
540+
} else {
541+
None
542+
}
543+
}
544+
545+
pub fn has_value(&self) -> bool {
546+
self.data().value().is_some() || (self.supports_text_ranges() && !self.is_multiline())
547+
}
537548
}
538549

539550
impl NodeState {
@@ -606,6 +617,10 @@ impl NodeState {
606617
pub fn raw_text_selection(&self) -> Option<&TextSelection> {
607618
self.data().text_selection()
608619
}
620+
621+
pub fn raw_value(&self) -> Option<&str> {
622+
self.data().value()
623+
}
609624
}
610625

611626
impl<'a> Node<'a> {
@@ -680,6 +695,7 @@ pub struct DetachedNode {
680695
pub(crate) is_focused: bool,
681696
pub(crate) is_root: bool,
682697
pub(crate) name: Option<String>,
698+
pub(crate) value: Option<String>,
683699
pub(crate) live: Live,
684700
pub(crate) supports_text_ranges: bool,
685701
}
@@ -697,6 +713,14 @@ impl DetachedNode {
697713
self.name.clone()
698714
}
699715

716+
pub fn value(&self) -> Option<String> {
717+
self.value.clone()
718+
}
719+
720+
pub fn has_value(&self) -> bool {
721+
self.value.is_some()
722+
}
723+
700724
pub fn live(&self) -> Live {
701725
self.live
702726
}

consumer/src/text.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ impl<'a> InnerPosition<'a> {
6060
}
6161

6262
fn is_paragraph_end(&self) -> bool {
63-
self.is_line_end() && self.node.value().unwrap().ends_with('\n')
63+
self.is_line_end() && self.node.data().value().unwrap().ends_with('\n')
6464
}
6565

6666
fn is_document_start(&self, root_node: &Node) -> bool {
@@ -237,7 +237,7 @@ impl<'a> Position<'a> {
237237
pub fn to_global_utf16_index(&self) -> usize {
238238
let mut total_length = 0usize;
239239
for node in self.root_node.inline_text_boxes() {
240-
let node_text = node.value().unwrap();
240+
let node_text = node.data().value().unwrap();
241241
if node.id() == self.inner.node.id() {
242242
let character_lengths = node.data().character_lengths();
243243
let slice_end = character_lengths[..self.inner.character_index]
@@ -538,7 +538,7 @@ impl<'a> Range<'a> {
538538
} else {
539539
character_lengths.len()
540540
};
541-
let value = node.value().unwrap();
541+
let value = node.data().value().unwrap();
542542
let s = if start_index == end_index {
543543
""
544544
} else if start_index == 0 && end_index == character_lengths.len() {
@@ -983,7 +983,7 @@ impl<'a> Node<'a> {
983983
pub fn text_position_from_global_utf16_index(&self, index: usize) -> Option<Position> {
984984
let mut total_length = 0usize;
985985
for node in self.inline_text_boxes() {
986-
let node_text = node.value().unwrap();
986+
let node_text = node.data().value().unwrap();
987987
let node_text_length = node_text.chars().map(char::len_utf16).sum::<usize>();
988988
let new_total_length = total_length + node_text_length;
989989
if index >= total_length && index < new_total_length {

consumer/src/tree.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ impl State {
182182
is_focused: old_focus_id == Some(id),
183183
is_root: old_root_id == id,
184184
name: None,
185+
value: None,
185186
live: Live::Off,
186187
supports_text_ranges: false,
187188
};

platforms/macos/src/event.rs

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@
33
// the LICENSE-APACHE file) or the MIT license (found in
44
// the LICENSE-MIT file), at your option.
55

6-
use accesskit::{Live, NodeId};
6+
use accesskit::{Live, NodeId, Role};
77
use accesskit_consumer::{DetachedNode, FilterResult, Node, TreeChangeHandler, TreeState};
88
use objc2::{
99
foundation::{NSInteger, NSMutableDictionary, NSNumber, NSObject, NSString},
1010
msg_send, Message,
1111
};
12-
use std::rc::Rc;
12+
use std::{collections::HashSet, rc::Rc};
1313

1414
use crate::{
1515
appkit::*,
@@ -132,13 +132,15 @@ impl QueuedEvents {
132132
pub(crate) struct EventGenerator {
133133
context: Rc<Context>,
134134
events: Vec<QueuedEvent>,
135+
text_changed: HashSet<NodeId>,
135136
}
136137

137138
impl EventGenerator {
138139
pub(crate) fn new(context: Rc<Context>) -> Self {
139140
Self {
140141
context,
141142
events: Vec::new(),
143+
text_changed: HashSet::new(),
142144
}
143145
}
144146

@@ -148,10 +150,56 @@ impl EventGenerator {
148150
events: self.events,
149151
}
150152
}
153+
154+
fn insert_text_change_if_needed_parent(&mut self, node: Node) {
155+
if !node.supports_text_ranges() {
156+
return;
157+
}
158+
let id = node.id();
159+
if self.text_changed.contains(&id) {
160+
return;
161+
}
162+
// Text change events must come before selection change
163+
// events. It doesn't matter if text change events come
164+
// before other events.
165+
self.events.insert(
166+
0,
167+
QueuedEvent::Generic {
168+
node_id: id,
169+
notification: unsafe { NSAccessibilityValueChangedNotification },
170+
},
171+
);
172+
self.text_changed.insert(id);
173+
}
174+
175+
fn insert_text_change_if_needed(&mut self, node: &Node) {
176+
if node.role() != Role::InlineTextBox {
177+
return;
178+
}
179+
if let Some(node) = node.filtered_parent(&filter) {
180+
self.insert_text_change_if_needed_parent(node);
181+
}
182+
}
183+
184+
fn insert_text_change_if_needed_for_removed_node(
185+
&mut self,
186+
node: &DetachedNode,
187+
current_state: &TreeState,
188+
) {
189+
if node.role() != Role::InlineTextBox {
190+
return;
191+
}
192+
if let Some(id) = node.parent_id() {
193+
if let Some(node) = current_state.node_by_id(id) {
194+
self.insert_text_change_if_needed_parent(node);
195+
}
196+
}
197+
}
151198
}
152199

153200
impl TreeChangeHandler for EventGenerator {
154201
fn node_added(&mut self, node: &Node) {
202+
self.insert_text_change_if_needed(node);
155203
if filter(node) != FilterResult::Include {
156204
return;
157205
}
@@ -162,7 +210,9 @@ impl TreeChangeHandler for EventGenerator {
162210
}
163211

164212
fn node_updated(&mut self, old_node: &DetachedNode, new_node: &Node) {
165-
// TODO: text changes, live regions
213+
if old_node.raw_value() != new_node.raw_value() {
214+
self.insert_text_change_if_needed(new_node);
215+
}
166216
if filter(new_node) != FilterResult::Include {
167217
return;
168218
}
@@ -218,7 +268,8 @@ impl TreeChangeHandler for EventGenerator {
218268
}
219269
}
220270

221-
fn node_removed(&mut self, node: &DetachedNode, _current_state: &TreeState) {
271+
fn node_removed(&mut self, node: &DetachedNode, current_state: &TreeState) {
272+
self.insert_text_change_if_needed_for_removed_node(node, current_state);
222273
self.events.push(QueuedEvent::NodeDestroyed(node.id()));
223274
}
224275
}

platforms/macos/src/node.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -303,13 +303,20 @@ impl<'a> NodeWrapper<'a> {
303303
}
304304
}
305305

306+
fn node_value(&self) -> Option<String> {
307+
match self {
308+
Self::Node(node) => node.value(),
309+
Self::DetachedNode(node) => node.value(),
310+
}
311+
}
312+
306313
// TODO: implement proper logic for title, description, and value;
307314
// see Chromium's content/browser/accessibility/browser_accessibility_cocoa.mm
308315
// and figure out how this is different in the macOS 10.10+ protocol
309316

310317
pub(crate) fn title(&self) -> Option<String> {
311318
let state = self.node_state();
312-
if state.role() == Role::StaticText && state.value().is_none() {
319+
if state.role() == Role::StaticText && state.raw_value().is_none() {
313320
// In this case, macOS wants the text to be the value, not title.
314321
return None;
315322
}
@@ -321,8 +328,8 @@ impl<'a> NodeWrapper<'a> {
321328
if let Some(state) = state.checked_state() {
322329
return Some(Value::Bool(state != CheckedState::False));
323330
}
324-
if let Some(value) = state.value() {
325-
return Some(Value::String(value.into()));
331+
if let Some(value) = self.node_value() {
332+
return Some(Value::String(value));
326333
}
327334
if let Some(value) = state.numeric_value() {
328335
return Some(Value::Number(value));

platforms/windows/src/adapter.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ impl Adapter {
112112
}
113113
}
114114
fn node_updated(&mut self, old_node: &DetachedNode, new_node: &Node) {
115-
if old_node.value() != new_node.value() {
115+
if old_node.raw_value() != new_node.raw_value() {
116116
self.insert_text_change_if_needed(new_node);
117117
}
118118
if filter(new_node) != FilterResult::Include {

platforms/windows/src/node.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -358,15 +358,21 @@ impl<'a> NodeWrapper<'a> {
358358
}
359359

360360
fn is_value_pattern_supported(&self) -> bool {
361-
self.node_state().value().is_some()
361+
match self {
362+
Self::Node(node) => node.has_value(),
363+
Self::DetachedNode(node) => node.has_value(),
364+
}
362365
}
363366

364367
fn is_range_value_pattern_supported(&self) -> bool {
365368
self.node_state().numeric_value().is_some()
366369
}
367370

368-
fn value(&self) -> &str {
369-
self.node_state().value().unwrap()
371+
fn value(&self) -> String {
372+
match self {
373+
Self::Node(node) => node.value().unwrap(),
374+
Self::DetachedNode(node) => node.value().unwrap(),
375+
}
370376
}
371377

372378
fn is_read_only(&self) -> bool {

0 commit comments

Comments
 (0)