Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion adapters/atspi-common/src/adapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
// found in the LICENSE.chromium file.

use crate::{
AdapterCallback, CacheEvent, Event, ObjectEvent, WindowEvent,
AdapterCallback, CacheEvent, DocumentEvent, Event, ObjectEvent, WindowEvent,
context::{ActionHandlerNoMut, ActionHandlerWrapper, AppContext, Context},
filters::filter,
node::{NodeIdOrRoot, NodeWrapper, PlatformNode, PlatformRoot},
Expand Down Expand Up @@ -510,6 +510,11 @@ impl Adapter {
.emit_event(self, Event::Object { target, event });
}

pub(crate) fn emit_document_event(&self, target: FullNodeId, event: DocumentEvent) {
self.callback
.emit_event(self, Event::Document { target, event });
}

fn emit_cache_added(&self, target: FullNodeId) {
self.callback
.emit_event(self, Event::Cache(CacheEvent::Added(target)));
Expand Down
9 changes: 9 additions & 0 deletions adapters/atspi-common/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ pub enum Event {
name: String,
event: WindowEvent,
},
Document {
target: FullNodeId,
event: DocumentEvent,
},
Cache(CacheEvent),
}

Expand Down Expand Up @@ -67,3 +71,8 @@ pub enum WindowEvent {
Activated,
Deactivated,
}

#[derive(Debug)]
pub enum DocumentEvent {
LoadComplete,
}
54 changes: 53 additions & 1 deletion adapters/atspi-common/src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use std::{
};

use crate::{
Action as AtspiAction, Error, ObjectEvent, Property, Rect as AtspiRect, Result,
Action as AtspiAction, DocumentEvent, Error, ObjectEvent, Property, Rect as AtspiRect, Result,
adapter::Adapter,
context::{AppContext, Context},
filters::filter,
Expand Down Expand Up @@ -336,6 +336,9 @@ impl NodeWrapper<'_> {
if atspi_role != AtspiRole::ToggleButton && state.toggled().is_some() {
atspi_state.insert(State::Checkable);
}
if state.is_busy() {
atspi_state.insert(State::Busy);
}
if state.is_modal() {
atspi_state.insert(State::Modal);
}
Expand Down Expand Up @@ -445,6 +448,29 @@ impl NodeWrapper<'_> {
self.0.raw_bounds().is_some() || self.is_root()
}

fn supports_document(&self) -> bool {
matches!(self.0.role(), Role::RootWebArea | Role::PdfRoot)
}

fn document_attributes(&self) -> HashMap<&'static str, String> {
let mut attributes = HashMap::new();
if let Some(title) = self.0.label() {
attributes.insert("title", title);
}
if let Some(uri) = self.0.url() {
attributes.insert("uri", uri.to_string());
}

attributes
}

fn document_attribute_value(&self, name: &str) -> Option<String> {
self.document_attributes()
.into_iter()
.find(|(key, _)| key.eq_ignore_ascii_case(name))
.map(|(_, value)| value)
}

fn supports_editable_text(&self) -> bool {
self.0.is_text_input() && self.0.supports_text_ranges()
}
Expand Down Expand Up @@ -473,6 +499,9 @@ impl NodeWrapper<'_> {
if self.supports_component() {
interfaces.insert(Interface::Component);
}
if self.supports_document() {
interfaces.insert(Interface::Document);
}
if self.supports_editable_text() {
interfaces.insert(Interface::EditableText);
}
Expand Down Expand Up @@ -559,6 +588,7 @@ impl NodeWrapper<'_> {
self.notify_property_changes(adapter, old);
self.notify_bounds_changes(window_bounds, adapter, old);
self.notify_children_changes(adapter, old);
self.notify_document_changes(adapter, old);
}

fn notify_state_changes(&self, adapter: &Adapter, old: &NodeWrapper<'_>) {
Expand Down Expand Up @@ -641,6 +671,12 @@ impl NodeWrapper<'_> {
}
}

fn notify_document_changes(&self, adapter: &Adapter, old: &NodeWrapper<'_>) {
if self.supports_document() && old.0.is_busy() && !self.0.is_busy() {
adapter.emit_document_event(self.id(), DocumentEvent::LoadComplete);
}
}

fn notify_children_changes(&self, adapter: &Adapter, old: &NodeWrapper<'_>) {
let old_filtered_children = old.filtered_child_ids().collect::<Vec<FullNodeId>>();
let new_filtered_children = self.filtered_child_ids().collect::<Vec<FullNodeId>>();
Expand Down Expand Up @@ -980,6 +1016,22 @@ impl PlatformNode {
})
}

pub fn supports_document(&self) -> Result<bool> {
self.resolve(|node| Ok(NodeWrapper(&node).supports_document()))
}

pub fn document_attributes(&self) -> Result<HashMap<&'static str, String>> {
self.resolve(|node| Ok(NodeWrapper(&node).document_attributes()))
}

pub fn document_attribute_value(&self, name: &str) -> Result<String> {
self.resolve(|node| {
Ok(NodeWrapper(&node)
.document_attribute_value(name)
.unwrap_or_default())
})
}

pub fn supports_editable_text(&self) -> Result<bool> {
self.resolve(|node| Ok(NodeWrapper(&node).supports_editable_text()))
}
Expand Down
37 changes: 35 additions & 2 deletions adapters/atspi-common/src/simplified.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
use std::collections::HashMap;

use crate::{
Adapter, CacheEvent, Event as EventEnum, NodeIdOrRoot, ObjectEvent, PlatformNode, PlatformRoot,
Property, WindowEvent,
Adapter, CacheEvent, DocumentEvent, Event as EventEnum, NodeIdOrRoot, ObjectEvent,
PlatformNode, PlatformRoot, Property, WindowEvent,
};

pub use crate::{
Expand Down Expand Up @@ -238,6 +238,27 @@ impl Accessible {
}
}

pub fn supports_document(&self) -> Result<bool> {
match self {
Self::Node(node) => node.supports_document(),
Self::Root(_) => Ok(false),
}
}

pub fn document_attributes(&self) -> Result<HashMap<&'static str, String>> {
match self {
Self::Node(node) => node.document_attributes(),
Self::Root(_) => Err(Error::UnsupportedInterface),
}
}

pub fn document_attribute_value(&self, name: &str) -> Result<String> {
match self {
Self::Node(node) => node.document_attribute_value(name),
Self::Root(_) => Err(Error::UnsupportedInterface),
}
}

pub fn supports_editable_text(&self) -> Result<bool> {
match self {
Self::Node(node) => node.supports_editable_text(),
Expand Down Expand Up @@ -777,6 +798,18 @@ impl Event {
data: Some(EventData::String(name)),
}
}
EventEnum::Document { target, event } => {
let kind = match event {
DocumentEvent::LoadComplete => "document:load-complete",
};
Self {
kind: kind.into(),
source: Accessible::Node(adapter.platform_node(target)),
detail1: 0,
detail2: 0,
data: None,
}
}
EventEnum::Cache(cache_event) => {
let (kind, target) = match cache_event {
CacheEvent::Added(target) => ("cache:add", target),
Expand Down
33 changes: 32 additions & 1 deletion adapters/unix/src/atspi/bus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ use crate::{
executor::{Executor, Task},
};
use accesskit_atspi_common::{
FullNodeId, NodeIdOrRoot, ObjectEvent, PlatformNode, PlatformRoot, Property, WindowEvent,
DocumentEvent, FullNodeId, NodeIdOrRoot, ObjectEvent, PlatformNode, PlatformRoot, Property,
WindowEvent,
};
use atspi::{
Interface, InterfaceSet, ObjectRefOwned,
Expand Down Expand Up @@ -141,6 +142,10 @@ impl Bus {
)
.await?;
}
if new_interfaces.contains(Interface::Document) {
self.register_interface(&path, DocumentInterface::new(node.clone()))
.await?;
}
if new_interfaces.contains(Interface::EditableText) {
self.register_interface(&path, EditableTextInterface::new(node.clone()))
.await?;
Expand Down Expand Up @@ -204,6 +209,10 @@ impl Bus {
self.unregister_interface::<ComponentInterface>(&path)
.await?;
}
if old_interfaces.contains(Interface::Document) {
self.unregister_interface::<DocumentInterface>(&path)
.await?;
}
if old_interfaces.contains(Interface::EditableText) {
self.unregister_interface::<EditableTextInterface>(&path)
.await?;
Expand Down Expand Up @@ -400,6 +409,28 @@ impl Bus {
.await
}

pub(crate) async fn emit_document_event(
&self,
adapter_id: usize,
target: FullNodeId,
event: DocumentEvent,
) -> Result<()> {
let target = ObjectId::Node {
adapter: adapter_id,
node: target,
};
let signal = match event {
DocumentEvent::LoadComplete => "LoadComplete",
};
self.emit_event(
target,
"org.a11y.atspi.Event.Document",
signal,
EventBodyBorrowed::default(),
)
.await
}

pub(crate) async fn emit_cache_add(&self, node: PlatformNode) -> Result<()> {
let Ok(item) = cache_item_for_node(self.unique_name().inner(), &node) else {
return Ok(());
Expand Down
60 changes: 60 additions & 0 deletions adapters/unix/src/atspi/interfaces/document.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Copyright 2026 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_atspi_common::PlatformNode;
use atspi::TextSelection;
use std::collections::HashMap;
use zbus::{fdo, interface};

fn unsupported() -> fdo::Error {
fdo::Error::NotSupported("document operation is not supported".into())
}

pub(crate) struct DocumentInterface(PlatformNode);

impl DocumentInterface {
pub fn new(node: PlatformNode) -> Self {
Self(node)
}

fn map_error(&self) -> impl '_ + FnOnce(accesskit_atspi_common::Error) -> fdo::Error {
|error| crate::util::map_error_from_node(&self.0, error)
}
}

#[interface(name = "org.a11y.atspi.Document")]
impl DocumentInterface {
#[zbus(property)]
fn current_page_number(&self) -> fdo::Result<i32> {
Err(unsupported())
}

#[zbus(property)]
fn page_count(&self) -> fdo::Result<i32> {
Err(unsupported())
}

fn get_attribute_value(&self, attribute_name: &str) -> fdo::Result<String> {
self.0
.document_attribute_value(attribute_name)
.map_err(self.map_error())
}

fn get_attributes(&self) -> fdo::Result<HashMap<&'static str, String>> {
self.0.document_attributes().map_err(self.map_error())
}

fn get_locale(&self) -> fdo::Result<String> {
Err(unsupported())
}

fn get_text_selections(&self) -> fdo::Result<Vec<TextSelection>> {
Err(unsupported())
}

fn set_text_selections(&self, _selections: Vec<TextSelection>) -> fdo::Result<bool> {
Err(unsupported())
}
}
2 changes: 2 additions & 0 deletions adapters/unix/src/atspi/interfaces/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ mod action;
mod application;
mod cache;
mod component;
mod document;
mod editable_text;
mod hyperlink;
mod selection;
Expand Down Expand Up @@ -36,6 +37,7 @@ pub(crate) use action::*;
pub(crate) use application::*;
pub(crate) use cache::*;
pub(crate) use component::*;
pub(crate) use document::*;
pub(crate) use editable_text::*;
pub(crate) use hyperlink::*;
pub(crate) use selection::*;
Expand Down
8 changes: 8 additions & 0 deletions adapters/unix/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,14 @@ async fn process_adapter_message(
.await?;
}
}
Message::EmitEvent {
adapter_id,
event: Event::Document { target, event },
} => {
if let Some(bus) = atspi_bus {
bus.emit_document_event(adapter_id, target, event).await?;
}
}
Message::EmitEvent {
event: Event::Cache(_),
..
Expand Down
Loading