commit e212f6811e0be1068bbc4de89de6bdba1ef8d75e
parent a02a3ce634015d230f8c167fdbd350ea849e04a6
Author: Eitan Isaacson <eitan@monotonous.org>
Date: Fri, 24 Oct 2025 14:31:34 +0000
Bug 1994455 - P2: Expose has-actions attribute when aria-actions is present. r=Jamie
We don't care about the value of the attribute or if there are any valid
targets, as long as it is present has-actions should be true.
Differential Revision: https://phabricator.services.mozilla.com/D268945
Diffstat:
5 files changed, 92 insertions(+), 0 deletions(-)
diff --git a/accessible/base/CacheConstants.h b/accessible/base/CacheConstants.h
@@ -178,6 +178,9 @@ class CacheKey {
// nsTArray<int32_t>, no domain
// As returned by HyperTextAccessibleBase::CachedHyperTextOffsets.
static constexpr nsStaticAtom* HyperTextOffsets = nsGkAtoms::offset;
+ // bool, CacheDomain::ARIA
+ // Accessible has aria-actions
+ static constexpr nsStaticAtom* HasActions = nsGkAtoms::hasActions;
// bool, CacheDomain::Actions
// Whether this image has a longdesc.
static constexpr nsStaticAtom* HasLongdesc = nsGkAtoms::longdesc;
diff --git a/accessible/generic/LocalAccessible.cpp b/accessible/generic/LocalAccessible.cpp
@@ -1149,6 +1149,10 @@ already_AddRefed<AccAttributes> LocalAccessible::Attributes() {
attribIter.ExposeAttr(attributes);
}
+ if (nsAccUtils::HasARIAAttr(Elm(), nsGkAtoms::aria_actions)) {
+ attributes->SetAttribute(nsGkAtoms::hasActions, true);
+ }
+
// If there is no aria-live attribute then expose default value of 'live'
// object attribute used for ARIA role of this accessible.
const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
@@ -1406,6 +1410,14 @@ void LocalAccessible::DOMAttributeChanged(int32_t aNameSpaceID,
}
}
+ if (aAttribute == nsGkAtoms::aria_actions && IsAdditionOrRemoval(aModType)) {
+ // We only care about the presence of aria-actions, not its value.
+ mDoc->QueueCacheUpdate(this, CacheDomain::ARIA);
+ RefPtr<AccEvent> event =
+ new AccObjectAttrChangedEvent(this, nsGkAtoms::hasActions);
+ mDoc->FireDelayedEvent(event);
+ }
+
dom::Element* elm = Elm();
if (HasNumericValue() &&
@@ -4036,6 +4048,12 @@ already_AddRefed<AccAttributes> LocalAccessible::BundleFieldsForCache(
} else if (IsUpdatePush(CacheDomain::ARIA)) {
fields->SetAttribute(CacheKey::ARIAAttributes, DeleteEntry());
}
+
+ if (nsAccUtils::HasARIAAttr(Elm(), nsGkAtoms::aria_actions)) {
+ fields->SetAttribute(CacheKey::HasActions, true);
+ } else if (IsUpdatePush(CacheDomain::ARIA)) {
+ fields->SetAttribute(CacheKey::HasActions, DeleteEntry());
+ }
}
if (aCacheDomain & CacheDomain::Relations && mContent) {
diff --git a/accessible/ipc/RemoteAccessible.cpp b/accessible/ipc/RemoteAccessible.cpp
@@ -1872,6 +1872,11 @@ already_AddRefed<AccAttributes> RemoteAccessible::Attributes() {
if (!popupType.IsEmpty()) {
attributes->SetAttribute(nsGkAtoms::ispopup, std::move(popupType));
}
+
+ if (auto hasActions =
+ mCachedFields->GetAttribute<bool>(CacheKey::HasActions)) {
+ attributes->SetAttribute(nsGkAtoms::hasActions, *hasActions);
+ }
}
nsAutoString name;
diff --git a/accessible/tests/browser/e10s/browser_caching_attributes.js b/accessible/tests/browser/e10s/browser_caching_attributes.js
@@ -761,3 +761,68 @@ addAccessibleTask(
},
{ chrome: true, topLevel: true }
);
+
+/**
+ * Test has-actions attribute.
+ */
+addAccessibleTask(
+ `<dialog aria-actions="btn" id="dlg" open>
+ Hello
+ <button id="btn">Close</button>
+ <button id="btn-hidden" hidden>Pin</button>
+ </dialog>`,
+ async function testHasActionsAttribute(browser, docAcc) {
+ function getDlgHasActions() {
+ try {
+ return dlg.attributes.getStringProperty("has-actions");
+ } catch (e) {
+ return null;
+ }
+ }
+
+ const dlg = findAccessibleChildByID(docAcc, "dlg");
+ is(getDlgHasActions(), "true", "dlg has-actions attribute is true");
+
+ // Removing the 'aria-actions' attribute from the element
+ // should remove the 'has-actions' attribute from the accessible.
+ let changed = waitForEvent(EVENT_OBJECT_ATTRIBUTE_CHANGED, "dlg");
+ await invokeSetAttribute(browser, "dlg", "aria-actions");
+ await changed;
+ await untilCacheIs(
+ getDlgHasActions,
+ null,
+ "dlg has-actions attribute removed"
+ );
+
+ // Setting the 'aria-actions' attribute to an empty string
+ // should make the 'has-actions' accessible attribute true.
+ changed = waitForEvent(EVENT_OBJECT_ATTRIBUTE_CHANGED, "dlg");
+ await invokeSetAttribute(browser, "dlg", "aria-actions", "");
+ await changed;
+ await untilCacheIs(
+ getDlgHasActions,
+ "true",
+ "dlg has-actions attribute re-added"
+ );
+
+ // Remove again to set up for next test
+ await invokeSetAttribute(browser, "dlg", "aria-actions");
+ await untilCacheIs(
+ getDlgHasActions,
+ null,
+ "dlg has-actions attribute removed again"
+ );
+
+ // Setting the 'aria-actions' attribute to a hidden target
+ // should still make 'has-actions' true
+ changed = waitForEvent(EVENT_OBJECT_ATTRIBUTE_CHANGED, "dlg");
+ await invokeSetAttribute(browser, "dlg", "aria-actions", "btn-hidden");
+ await changed;
+ await untilCacheIs(
+ getDlgHasActions,
+ "true",
+ "dlg has-actions attribute re-added with hidden target"
+ );
+ },
+ { chrome: true, topLevel: true }
+);
diff --git a/xpcom/ds/StaticAtoms.py b/xpcom/ds/StaticAtoms.py
@@ -516,6 +516,7 @@ STATIC_ATOMS = [
Atom("handler", "handler"),
Atom("handlers", "handlers"),
Atom("HARD", "HARD"),
+ Atom("hasActions", "has-actions"),
Atom("hasSameNode", "has-same-node"),
Atom("hbox", "hbox"),
Atom("head", "head"),