commit 7224b9290783af14f443f77a07f0d82d45331a5d
parent a44e6ff6ffc043f664d03812775b7cb00ff7ff68
Author: Eitan Isaacson <eitan@monotonous.org>
Date: Tue, 4 Nov 2025 15:17:24 +0000
Bug 1992028 - Implement details-from attribute. r=Jamie
This allows ATs to distinguish the source of details relations.
Differential Revision: https://phabricator.services.mozilla.com/D271153
Diffstat:
5 files changed, 64 insertions(+), 3 deletions(-)
diff --git a/accessible/generic/LocalAccessible.cpp b/accessible/generic/LocalAccessible.cpp
@@ -1169,6 +1169,22 @@ already_AddRefed<AccAttributes> LocalAccessible::Attributes() {
}
}
+ nsString detailsFrom;
+ AssociatedElementsIterator iter(mDoc, Elm(), nsGkAtoms::aria_details);
+ if (iter.Next()) {
+ detailsFrom.AssignLiteral("aria-details");
+ } else if (GetCommandForDetailsRelation()) {
+ detailsFrom.AssignLiteral("command-for");
+ } else if (GetPopoverTargetDetailsRelation()) {
+ detailsFrom.AssignLiteral("popover-target");
+ } else if (GetAnchorPositionTargetDetailsRelation()) {
+ detailsFrom.AssignLiteral("css-anchor");
+ }
+
+ if (!detailsFrom.IsEmpty()) {
+ attributes->SetAttribute(nsGkAtoms::details_from, std::move(detailsFrom));
+ }
+
return attributes.forget();
}
diff --git a/accessible/ipc/RemoteAccessible.cpp b/accessible/ipc/RemoteAccessible.cpp
@@ -1752,8 +1752,8 @@ already_AddRefed<AccAttributes> RemoteAccessible::Attributes() {
CacheDomain::State | // State
CacheDomain::Viewport | // State
CacheDomain::Table | // TableIsProbablyForLayout
- CacheDomain::DOMNodeIDAndClass // DOMNodeID
- )) {
+ CacheDomain::DOMNodeIDAndClass | // DOMNodeID
+ CacheDomain::Relations)) {
return attributes.forget();
}
@@ -1896,6 +1896,21 @@ already_AddRefed<AccAttributes> RemoteAccessible::Attributes() {
attributes->SetAttribute(nsGkAtoms::aria_valuetext, std::move(valuetext));
}
+ nsString detailsFrom;
+ if (mCachedFields->HasAttribute(nsGkAtoms::aria_details)) {
+ detailsFrom.AssignLiteral("aria-details");
+ } else if (mCachedFields->HasAttribute(nsGkAtoms::commandfor)) {
+ detailsFrom.AssignLiteral("command-for");
+ } else if (mCachedFields->HasAttribute(nsGkAtoms::popovertarget)) {
+ detailsFrom.AssignLiteral("popover-target");
+ } else if (mCachedFields->HasAttribute(nsGkAtoms::target)) {
+ detailsFrom.AssignLiteral("css-anchor");
+ }
+
+ if (!detailsFrom.IsEmpty()) {
+ attributes->SetAttribute(nsGkAtoms::details_from, std::move(detailsFrom));
+ }
+
return attributes.forget();
}
diff --git a/accessible/tests/browser/relations/browser_anchor_positioning.js b/accessible/tests/browser/relations/browser_anchor_positioning.js
@@ -76,6 +76,11 @@ addAccessibleTask(
const target1 = findAccessibleChildByID(docAcc, "target1");
await testDetailsRelations(btn1, target1);
+ is(
+ btn1.attributes.getStringProperty("details-from"),
+ "css-anchor",
+ "Correct details-from attribute"
+ );
info("Make anchor invalid");
await invokeContentTaskAndTick(browser, [], () => {
Object.assign(content.document.getElementById("btn1").style, {
@@ -448,7 +453,7 @@ addAccessibleTask(
<div id="target-targetsetdetails" aria-details="" class="target">World</div>
<button id="btn-targetsetdetails">Hello</button>
`,
- async function testTooltipPositionAnchor(browser, docAcc) {
+ async function testOtherRelationsWithAnchor(browser, docAcc) {
info("Test no details relations when explicit relations are set");
const btnDescribedby = findAccessibleChildByID(docAcc, "btn-describedby");
const targetDescribedby = findAccessibleChildByID(
diff --git a/accessible/tests/browser/relations/browser_popover_and_command.js b/accessible/tests/browser/relations/browser_popover_and_command.js
@@ -42,6 +42,12 @@ addAccessibleTask(
await testCachedRelation(toggleSibling, RELATION_DETAILS, []);
await testCachedRelation(popover, RELATION_DETAILS_FOR, toggle1);
+ is(
+ toggle1.attributes.getStringProperty("details-from"),
+ "popover-target",
+ "Correct details-from attribute"
+ );
+
info("Setting toggle2 popovertarget");
await invokeSetAttribute(browser, "toggle2", "popovertarget", "popover");
await testCachedRelation(toggle2, RELATION_DETAILS, popover);
@@ -134,10 +140,16 @@ addAccessibleTask(
// of the popover.
await testCachedRelation(toggleSibling, RELATION_DETAILS, []);
await testCachedRelation(popover, RELATION_DETAILS_FOR, [toggle1, show]);
+ is(
+ toggle1.attributes.getStringProperty("details-from"),
+ "command-for",
+ "Correct details-from attribute"
+ );
info("Setting toggle2 commandfor");
await invokeSetAttribute(browser, "toggle2", "commandfor", "popover");
await testCachedRelation(toggle2, RELATION_DETAILS, popover);
+
await testCachedRelation(popover, RELATION_DETAILS_FOR, [
toggle1,
show,
@@ -154,6 +166,12 @@ addAccessibleTask(
const details = findAccessibleChildByID(docAcc, "details");
// aria-details overrides popover.
await testCachedRelation(toggle1, RELATION_DETAILS, details);
+ is(
+ toggle1.attributes.getStringProperty("details-from"),
+ "aria-details",
+ "Correct details-from attribute"
+ );
+
await testCachedRelation(popover, RELATION_DETAILS_FOR, [show]);
info("Removing aria-details from toggle1");
@@ -161,6 +179,12 @@ addAccessibleTask(
await testCachedRelation(toggle1, RELATION_DETAILS, popover);
await testCachedRelation(popover, RELATION_DETAILS_FOR, [toggle1, show]);
+ is(
+ toggle1.attributes.getStringProperty("details-from"),
+ "command-for",
+ "Correct details-from attribute"
+ );
+
info("Hiding popover");
let hidden = waitForEvent(EVENT_HIDE, popover);
toggle1.doAction(0);
diff --git a/xpcom/ds/StaticAtoms.py b/xpcom/ds/StaticAtoms.py
@@ -340,6 +340,7 @@ STATIC_ATOMS = [
Atom("description", "description"),
Atom("destructor", "destructor"),
Atom("details", "details"),
+ Atom("details_from", "details-from"),
Atom("deviceAspectRatio", "device-aspect-ratio"),
Atom("deviceHeight", "device-height"),
Atom("devicePixelRatio", "device-pixel-ratio"),