0

[A11y] Expose presence of potential interest target

An interesttarget is an experimental HTML attribute that, along with
an interestaction, allows for the target's visibility to be toggled
on mouse hover. However, an AT user must be informed that there
is potentially an object there.

- Expose a new object attribute for "rich" interest targets that are not just plain text (plain text targets are exposed as descriptions)
- Unlike the details relation, the state is exposed even if if the
target is currently hidden

This will allow ATs to inform users that there is an interest target
present, so that they can use the command to bring it up
(e.g. ctrl+insert+enter  should work on JAWS, which routes the
mouse cursor to the object to trigger any mouseover effects).

Fixed: 377923492
Change-Id: I6103ce77154e94b14d67f6195888efc616e220f6
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6012299
Commit-Queue: Aaron Leventhal <aleventhal@chromium.org>
Reviewed-by: David Tseng <dtseng@chromium.org>
Reviewed-by: Mark Schillaci <mschillaci@google.com>
Reviewed-by: Dominic Farolino <dom@chromium.org>
Auto-Submit: Aaron Leventhal <aleventhal@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1383299}
This commit is contained in:
Aaron Leventhal
2024-11-14 22:59:47 +00:00
committed by Chromium LUCI CQ
parent d535bdcfe9
commit d62ef43218
12 changed files with 36 additions and 17 deletions
chrome/browser/resources/chromeos/accessibility/definitions
content/test/data/accessibility/html
extensions/common/api
third_party/blink/renderer/modules/accessibility
ui/accessibility

@ -358,6 +358,7 @@ declare namespace chrome {
FOCUSABLE = 'focusable',
FOCUSED = 'focused',
HAS_ACTIONS = 'hasActions',
HAS_INTEREST_TARGET = 'hasInterestTarget',
HORIZONTAL = 'horizontal',
HOVERED = 'hovered',
IGNORED = 'ignored',

@ -1,7 +1,7 @@
[document web]
++[section] details-for=[push button]
++++[entry] selectable-text
++[push button] name='Button' details=[section] details-from:interest-target details-roles:*
++[push button] name='Button' details=[section] details-from:interest-target details-roles:* has-interest-target:true
++[push button] name='Button'
++[section]
++++[entry] selectable-text

@ -4,7 +4,7 @@ rootWebArea
++++++genericContainer
++++++++textField
++++++++++genericContainer
++++++button name='Button' detailsFrom=interestTarget detailsIds=genericContainer
++++++button hasInterestTarget name='Button' detailsFrom=interestTarget detailsIds=genericContainer
++++++++staticText name='Button'
++++++++++inlineTextBox name='Button'
++++++button name='Button'

@ -1,7 +1,7 @@
ROLE_SYSTEM_DOCUMENT READONLY FOCUSABLE
++IA2_ROLE_SECTION
++++ROLE_SYSTEM_TEXT FOCUSABLE
++ROLE_SYSTEM_PUSHBUTTON name='Button' FOCUSABLE details-from:interest-target details-roles:*
++ROLE_SYSTEM_PUSHBUTTON name='Button' FOCUSABLE has-interest-target:true details-from:interest-target details-roles:*
++ROLE_SYSTEM_PUSHBUTTON name='Button' FOCUSABLE
++IA2_ROLE_SECTION
++++ROLE_SYSTEM_TEXT FOCUSABLE

@ -5,14 +5,17 @@
@WIN-ALLOW:EXPANDED*
@WIN-ALLOW:HASPOPUP*
@WIN-ALLOW:haspopup*
@WIN-ALLOW:has-interest-target*
@BLINK-ALLOW:details*
@BLINK-ALLOW:desc*
@BLINK-ALLOW:haspopup*
@BLINK-ALLOW:hasInterestTarget*
@BLINK-ALLOW:expanded*
@AURALINUX-ALLOW:details-from*
@AURALINUX-ALLOW:details-roles*
@AURALINUX-ALLOW:expanded*
@AURALINUX-ALLOW:haspopup:*
@AURALINUX-ALLOW:has-interest-target*
-->
<!-- Rich interest target -->

@ -383,7 +383,8 @@
richlyEditable,
vertical,
visited,
hasActions
hasActions,
hasInterestTarget
};
// All possible actions that can be performed on automation nodes.

@ -2564,7 +2564,7 @@ void AXObject::SerializeUnignoredAttributes(ui::AXNodeData* node_data,
}
}
// Check for presence of aria-actions. Even if the value is empty because the
// Check for presence of aria-actions. Even if the value is empty or the
// targets are hidden, we still want to expose that there could be actions.
if (RuntimeEnabledFeatures::AriaActionsEnabled() &&
HasAriaAttribute(html_names::kAriaActionsAttr)) {
@ -2611,6 +2611,19 @@ void AXObject::SerializeComputedDetailsRelation(
return;
}
// Add aria-details for a interest target.
if (AXObject* interest_popover = GetInterestTargetForInvoker()) {
// Add state even if the target is hidden.
node_data->AddState(ax::mojom::blink::State::kHasInterestTarget);
if (interest_popover->IsVisible()) {
node_data->AddIntListAttribute(
ax::mojom::blink::IntListAttribute::kDetailsIds,
{static_cast<int32_t>(interest_popover->AXObjectID())});
node_data->SetDetailsFrom(ax::mojom::blink::DetailsFrom::kInterestTarget);
return;
}
}
// Add aria-details for a popover invoker.
if (AXObject* popover = GetPopoverTargetForInvoker()) {
node_data->AddIntListAttribute(
@ -2620,15 +2633,6 @@ void AXObject::SerializeComputedDetailsRelation(
return;
}
// Add aria-details for a popover invoker.
if (AXObject* interest_popover = GetInterestTargetForInvoker()) {
node_data->AddIntListAttribute(
ax::mojom::blink::IntListAttribute::kDetailsIds,
{static_cast<int32_t>(interest_popover->AXObjectID())});
node_data->SetDetailsFrom(ax::mojom::blink::DetailsFrom::kInterestTarget);
return;
}
// Add aria-details for the element anchored to this object.
if (AXObject* positioned_obj = GetPositionedObjectForAnchor(node_data)) {
node_data->AddIntListAttribute(
@ -2719,7 +2723,7 @@ AXObject* AXObject::GetInterestTargetForInvoker() const {
return nullptr;
}
return ax_popover->IsVisible() ? ax_popover : nullptr;
return ax_popover;
}
AXObject* AXObject::GetPositionedObjectForAnchor(ui::AXNodeData* data) const {

@ -905,6 +905,8 @@ class MODULES_EXPORT AXObject : public GarbageCollected<AXObject> {
AXObject* GetPopoverTargetForInvoker() const;
// Heuristic to get the interest target for an invoking element.
// Returns null if the interest target points to plain content and can be
// expose as a description instead.
AXObject* GetInterestTargetForInvoker() const;
// Elements can be positioned relative to other elements with CSS anchor

@ -1018,6 +1018,8 @@ const char* ToString(ax::mojom::State state) {
return "focusable";
case ax::mojom::State::kHasActions:
return "hasActions";
case ax::mojom::State::kHasInterestTarget:
return "hasInterestTarget";
case ax::mojom::State::kHorizontal:
return "horizontal";
case ax::mojom::State::kHovered:

@ -359,8 +359,8 @@ enum Role {
// Bitfield value. Each state can either be true or false.
//
// Next version: 2
// Next value: 20
// Next version: 3
// Next value: 21
[Extensible, Stable, Uuid="35e7123a-f31f-4de7-94a4-3412dcb6bd5a"]
enum State {
[Default]kNone = 0,
@ -386,6 +386,7 @@ enum State {
kVertical = 17,
kVisited = 18,
[MinVersion=1] kHasActions = 19,
[MinVersion=2] kHasInterestTarget = 20,
};
// An action to be taken on an accessibility node.

@ -1373,6 +1373,10 @@ void AXPlatformNodeBase::ComputeAttributes(PlatformAttributeList* attributes) {
AddAttributeToList("haspopup", "menu", attributes);
}
if (HasState(ax::mojom::State::kHasInterestTarget)) {
AddAttributeToList("has-interest-target", "true", attributes);
}
// Expose the aria-ispopup attribute.
int32_t is_popup;
if (GetIntAttribute(ax::mojom::IntAttribute::kIsPopup, &is_popup)) {

@ -628,6 +628,7 @@ const char* const ATK_OBJECT_ATTRIBUTES[] = {
"explicit-name",
"grabbed",
"haspopup",
"has-interest-target",
"hidden",
"id",
"keyshortcuts",