Diff
Added: trunk/LayoutTests/accessibility/grid-with-aria-owned-cells-expected.txt (0 => 294878)
--- trunk/LayoutTests/accessibility/grid-with-aria-owned-cells-expected.txt (rev 0)
+++ trunk/LayoutTests/accessibility/grid-with-aria-owned-cells-expected.txt 2022-05-26 16:01:38 UTC (rev 294878)
@@ -0,0 +1,12 @@
+This tests that a grid can use aria-owns to establish parent-child relationships between its rows and cells, even when the cell elements are not descendants of the grid in the DOM hierarchy.
+PASS: row1.childAtIndex(0).isEqual(cell_1_1) === true
+PASS: cell_1_1.parentElement().isEqual(row1) === true
+PASS: row3.childAtIndex(13).isEqual(cell_3_14) === true
+PASS: cell_3_14.parentElement().isEqual(row3) === true
+PASS: row5.childAtIndex(27).isEqual(cell_5_28) === true
+PASS: cell_5_28.parentElement().isEqual(row5) === true
+
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
Added: trunk/LayoutTests/accessibility/grid-with-aria-owned-cells.html (0 => 294878)
--- trunk/LayoutTests/accessibility/grid-with-aria-owned-cells.html (rev 0)
+++ trunk/LayoutTests/accessibility/grid-with-aria-owned-cells.html 2022-05-26 16:01:38 UTC (rev 294878)
@@ -0,0 +1,197 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src=""
+<script src=""
+</head>
+<body>
+
+<div id="content">
+
+<div id="grid" role="grid">
+ <div id="row1" role="row" aria-owns="cell-1-1 cell-1-2 cell-1-3 cell-1-4 cell-1-5 cell-1-6 cell-1-7 cell-1-8 cell-1-9 cell-1-10 cell-1-11 cell-1-12 cell-1-13 cell-1-14 cell-1-15 cell-1-16 cell-1-17 cell-1-18 cell-1-19 cell-1-20 cell-1-21 cell-1-22 cell-1-23 cell-1-24 cell-1-25 cell-1-26 cell-1-27 cell-1-28"></div>
+ <div role="row" aria-owns="cell-2-1 cell-2-2 cell-2-3 cell-2-4 cell-2-5 cell-2-6 cell-2-7 cell-2-8 cell-2-9 cell-2-10 cell-2-11 cell-2-12 cell-2-13 cell-2-14 cell-2-15 cell-2-16 cell-2-17 cell-2-18 cell-2-19 cell-2-20 cell-2-21 cell-2-22 cell-2-23 cell-2-24 cell-2-25 cell-2-26 cell-2-27 cell-2-28"></div>
+ <div id="row3" role="row" aria-owns="cell-3-1 cell-3-2 cell-3-3 cell-3-4 cell-3-5 cell-3-6 cell-3-7 cell-3-8 cell-3-9 cell-3-10 cell-3-11 cell-3-12 cell-3-13 cell-3-14 cell-3-15 cell-3-16 cell-3-17 cell-3-18 cell-3-19 cell-3-20 cell-3-21 cell-3-22 cell-3-23 cell-3-24 cell-3-25 cell-3-26 cell-3-27 cell-3-28"></div>
+ <div role="row" aria-owns="cell-4-1 cell-4-2 cell-4-3 cell-4-4 cell-4-5 cell-4-6 cell-4-7 cell-4-8 cell-4-9 cell-4-10 cell-4-11 cell-4-12 cell-4-13 cell-4-14 cell-4-15 cell-4-16 cell-4-17 cell-4-18 cell-4-19 cell-4-20 cell-4-21 cell-4-22 cell-4-23 cell-4-24 cell-4-25 cell-4-26 cell-4-27 cell-4-28"></div>
+ <div id="row5" role="row" aria-owns="cell-5-1 cell-5-2 cell-5-3 cell-5-4 cell-5-5 cell-5-6 cell-5-7 cell-5-8 cell-5-9 cell-5-10 cell-5-11 cell-5-12 cell-5-13 cell-5-14 cell-5-15 cell-5-16 cell-5-17 cell-5-18 cell-5-19 cell-5-20 cell-5-21 cell-5-22 cell-5-23 cell-5-24 cell-5-25 cell-5-26 cell-5-27 cell-5-28"></div>
+</div>
+
+ <div class="row">
+ <span role="gridcell" class="cell" id="cell-1-1">Hello</span>
+ <span role="gridcell" class="cell" id="cell-1-2">World</span>
+ <span role="gridcell" class="cell" id="cell-1-3">Hello</span>
+ <span role="gridcell" class="cell" id="cell-1-4">World</span>
+ <span role="gridcell" class="cell" id="cell-1-5">Hello</span>
+ <span role="gridcell" class="cell" id="cell-1-6">World</span>
+ <span role="gridcell" class="cell" id="cell-1-7">Hello</span>
+ <span role="gridcell" class="cell" id="cell-1-8">World</span>
+ <span role="gridcell" class="cell" id="cell-1-9">Hello</span>
+ <span role="gridcell" class="cell" id="cell-1-10">World</span>
+ <span role="gridcell" class="cell" id="cell-1-11">Hello</span>
+ <span role="gridcell" class="cell" id="cell-1-12">World</span>
+ <span role="gridcell" class="cell" id="cell-1-13">Hello</span>
+ <span role="gridcell" class="cell" id="cell-1-14">World</span>
+ <span role="gridcell" class="cell" id="cell-1-15">Hello</span>
+ <span role="gridcell" class="cell" id="cell-1-16">World</span>
+ <span role="gridcell" class="cell" id="cell-1-17">Hello</span>
+ <span role="gridcell" class="cell" id="cell-1-18">World</span>
+ <span role="gridcell" class="cell" id="cell-1-19">Hello</span>
+ <span role="gridcell" class="cell" id="cell-1-20">Hello</span>
+ <span role="gridcell" class="cell" id="cell-1-21">Hello</span>
+ <span role="gridcell" class="cell" id="cell-1-22">Hello</span>
+ <span role="gridcell" class="cell" id="cell-1-23">Hello</span>
+ <span role="gridcell" class="cell" id="cell-1-24">Hello</span>
+ <span role="gridcell" class="cell" id="cell-1-25">Hello</span>
+ <span role="gridcell" class="cell" id="cell-1-26">Hello</span>
+ <span role="gridcell" class="cell" id="cell-1-27">Hello</span>
+ <span role="gridcell" class="cell" id="cell-1-28">Hello</span>
+ </div>
+ <div class="row">
+ <span role="gridcell" class="cell" id="cell-2-1">Hello</span>
+ <span role="gridcell" class="cell" id="cell-2-2">World</span>
+ <span role="gridcell" class="cell" id="cell-2-3">Hello</span>
+ <span role="gridcell" class="cell" id="cell-2-4">World</span>
+ <span role="gridcell" class="cell" id="cell-2-5">Hello</span>
+ <span role="gridcell" class="cell" id="cell-2-6">World</span>
+ <span role="gridcell" class="cell" id="cell-2-7">Hello</span>
+ <span role="gridcell" class="cell" id="cell-2-8">World</span>
+ <span role="gridcell" class="cell" id="cell-2-9">Hello</span>
+ <span role="gridcell" class="cell" id="cell-2-10">World</span>
+ <span role="gridcell" class="cell" id="cell-2-11">Hello</span>
+ <span role="gridcell" class="cell" id="cell-2-12">World</span>
+ <span role="gridcell" class="cell" id="cell-2-13">Hello</span>
+ <span role="gridcell" class="cell" id="cell-2-14">World</span>
+ <span role="gridcell" class="cell" id="cell-2-15">Hello</span>
+ <span role="gridcell" class="cell" id="cell-2-16">World</span>
+ <span role="gridcell" class="cell" id="cell-2-17">Hello</span>
+ <span role="gridcell" class="cell" id="cell-2-18">World</span>
+ <span role="gridcell" class="cell" id="cell-2-19">Hello</span>
+ <span role="gridcell" class="cell" id="cell-2-20">Hello</span>
+ <span role="gridcell" class="cell" id="cell-2-21">Hello</span>
+ <span role="gridcell" class="cell" id="cell-2-22">Hello</span>
+ <span role="gridcell" class="cell" id="cell-2-23">Hello</span>
+ <span role="gridcell" class="cell" id="cell-2-24">Hello</span>
+ <span role="gridcell" class="cell" id="cell-2-25">Hello</span>
+ <span role="gridcell" class="cell" id="cell-2-26">Hello</span>
+ <span role="gridcell" class="cell" id="cell-2-27">Hello</span>
+ <span role="gridcell" class="cell" id="cell-2-28">Hello</span>
+ </div>
+ <div class="row">
+ <span role="gridcell" class="cell" id="cell-3-1">Hello</span>
+ <span role="gridcell" class="cell" id="cell-3-2">World</span>
+ <span role="gridcell" class="cell" id="cell-3-3">Hello</span>
+ <span role="gridcell" class="cell" id="cell-3-4">World</span>
+ <span role="gridcell" class="cell" id="cell-3-5">Hello</span>
+ <span role="gridcell" class="cell" id="cell-3-6">World</span>
+ <span role="gridcell" class="cell" id="cell-3-7">Hello</span>
+ <span role="gridcell" class="cell" id="cell-3-8">World</span>
+ <span role="gridcell" class="cell" id="cell-3-9">Hello</span>
+ <span role="gridcell" class="cell" id="cell-3-10">World</span>
+ <span role="gridcell" class="cell" id="cell-3-11">Hello</span>
+ <span role="gridcell" class="cell" id="cell-3-12">World</span>
+ <span role="gridcell" class="cell" id="cell-3-13">Hello</span>
+ <span role="gridcell" class="cell" id="cell-3-14">World</span>
+ <span role="gridcell" class="cell" id="cell-3-15">Hello</span>
+ <span role="gridcell" class="cell" id="cell-3-16">World</span>
+ <span role="gridcell" class="cell" id="cell-3-17">Hello</span>
+ <span role="gridcell" class="cell" id="cell-3-18">World</span>
+ <span role="gridcell" class="cell" id="cell-3-19">Hello</span>
+ <span role="gridcell" class="cell" id="cell-3-20">Hello</span>
+ <span role="gridcell" class="cell" id="cell-3-21">Hello</span>
+ <span role="gridcell" class="cell" id="cell-3-22">Hello</span>
+ <span role="gridcell" class="cell" id="cell-3-23">Hello</span>
+ <span role="gridcell" class="cell" id="cell-3-24">Hello</span>
+ <span role="gridcell" class="cell" id="cell-3-25">Hello</span>
+ <span role="gridcell" class="cell" id="cell-3-26">Hello</span>
+ <span role="gridcell" class="cell" id="cell-3-27">Hello</span>
+ <span role="gridcell" class="cell" id="cell-3-28">Hello</span>
+ </div>
+ <div class="row">
+ <span role="gridcell" class="cell" id="cell-4-1">Hello</span>
+ <span role="gridcell" class="cell" id="cell-4-2">World</span>
+ <span role="gridcell" class="cell" id="cell-4-3">Hello</span>
+ <span role="gridcell" class="cell" id="cell-4-4">World</span>
+ <span role="gridcell" class="cell" id="cell-4-5">Hello</span>
+ <span role="gridcell" class="cell" id="cell-4-6">World</span>
+ <span role="gridcell" class="cell" id="cell-4-7">Hello</span>
+ <span role="gridcell" class="cell" id="cell-4-8">World</span>
+ <span role="gridcell" class="cell" id="cell-4-9">Hello</span>
+ <span role="gridcell" class="cell" id="cell-4-10">World</span>
+ <span role="gridcell" class="cell" id="cell-4-11">Hello</span>
+ <span role="gridcell" class="cell" id="cell-4-12">World</span>
+ <span role="gridcell" class="cell" id="cell-4-13">Hello</span>
+ <span role="gridcell" class="cell" id="cell-4-14">World</span>
+ <span role="gridcell" class="cell" id="cell-4-15">Hello</span>
+ <span role="gridcell" class="cell" id="cell-4-16">World</span>
+ <span role="gridcell" class="cell" id="cell-4-17">Hello</span>
+ <span role="gridcell" class="cell" id="cell-4-18">World</span>
+ <span role="gridcell" class="cell" id="cell-4-19">Hello</span>
+ <span role="gridcell" class="cell" id="cell-4-20">Hello</span>
+ <span role="gridcell" class="cell" id="cell-4-21">Hello</span>
+ <span role="gridcell" class="cell" id="cell-4-22">Hello</span>
+ <span role="gridcell" class="cell" id="cell-4-23">Hello</span>
+ <span role="gridcell" class="cell" id="cell-4-24">Hello</span>
+ <span role="gridcell" class="cell" id="cell-4-25">Hello</span>
+ <span role="gridcell" class="cell" id="cell-4-26">Hello</span>
+ <span role="gridcell" class="cell" id="cell-4-27">Hello</span>
+ <span role="gridcell" class="cell" id="cell-4-28">Hello</span>
+ </div>
+ <div class="row">
+ <span role="gridcell" class="cell" id="cell-5-1">Hello</span>
+ <span role="gridcell" class="cell" id="cell-5-2">World</span>
+ <span role="gridcell" class="cell" id="cell-5-3">Hello</span>
+ <span role="gridcell" class="cell" id="cell-5-4">World</span>
+ <span role="gridcell" class="cell" id="cell-5-5">Hello</span>
+ <span role="gridcell" class="cell" id="cell-5-6">World</span>
+ <span role="gridcell" class="cell" id="cell-5-7">Hello</span>
+ <span role="gridcell" class="cell" id="cell-5-8">World</span>
+ <span role="gridcell" class="cell" id="cell-5-9">Hello</span>
+ <span role="gridcell" class="cell" id="cell-5-10">World</span>
+ <span role="gridcell" class="cell" id="cell-5-11">Hello</span>
+ <span role="gridcell" class="cell" id="cell-5-12">World</span>
+ <span role="gridcell" class="cell" id="cell-5-13">Hello</span>
+ <span role="gridcell" class="cell" id="cell-5-14">World</span>
+ <span role="gridcell" class="cell" id="cell-5-15">Hello</span>
+ <span role="gridcell" class="cell" id="cell-5-16">World</span>
+ <span role="gridcell" class="cell" id="cell-5-17">Hello</span>
+ <span role="gridcell" class="cell" id="cell-5-18">World</span>
+ <span role="gridcell" class="cell" id="cell-5-19">Hello</span>
+ <span role="gridcell" class="cell" id="cell-5-20">Hello</span>
+ <span role="gridcell" class="cell" id="cell-5-21">Hello</span>
+ <span role="gridcell" class="cell" id="cell-5-22">Hello</span>
+ <span role="gridcell" class="cell" id="cell-5-23">Hello</span>
+ <span role="gridcell" class="cell" id="cell-5-24">Hello</span>
+ <span role="gridcell" class="cell" id="cell-5-25">Hello</span>
+ <span role="gridcell" class="cell" id="cell-5-26">Hello</span>
+ <span role="gridcell" class="cell" id="cell-5-27">Hello</span>
+ <span role="gridcell" class="cell" id="cell-5-28">Hello</span>
+ </div>
+
+</div>
+
+<script>
+ if (window.accessibilityController) {
+ let output = "This tests that a grid can use aria-owns to establish parent-child relationships between its rows and cells, even when the cell elements are not descendants of the grid in the DOM hierarchy.\n";
+
+ var table = accessibilityController.accessibleElementById("grid");
+ var row1 = accessibilityController.accessibleElementById("row1");
+ var cell_1_1 = table.cellForColumnAndRow(0, 0);
+ output += expect("row1.childAtIndex(0).isEqual(cell_1_1)", "true");
+ output += expect("cell_1_1.parentElement().isEqual(row1)", "true");
+
+ var row3 = accessibilityController.accessibleElementById("row3");
+ var cell_3_14 = table.cellForColumnAndRow(13, 2);
+ output += expect("row3.childAtIndex(13).isEqual(cell_3_14)", "true");
+ output += expect("cell_3_14.parentElement().isEqual(row3)", "true");
+
+ var row5 = accessibilityController.accessibleElementById("row5");
+ var cell_5_28 = table.cellForColumnAndRow(27, 4);
+ output += expect("row5.childAtIndex(27).isEqual(cell_5_28)", "true");
+ output += expect("cell_5_28.parentElement().isEqual(row5)", "true");
+
+ document.getElementById("content").style.visibility = "hidden";
+ debug(output);
+ }
+</script>
+</body>
+</html>
Modified: trunk/LayoutTests/platform/win/TestExpectations (294877 => 294878)
--- trunk/LayoutTests/platform/win/TestExpectations 2022-05-26 14:00:35 UTC (rev 294877)
+++ trunk/LayoutTests/platform/win/TestExpectations 2022-05-26 16:01:38 UTC (rev 294878)
@@ -943,6 +943,7 @@
# TODO ARIA 1.1 table related attributes are not supported
webkit.org/b/148967 accessibility/aria-table-attributes.html [ Skip ]
accessibility/aria-grid-with-aria-owns-rows.html [ Skip ]
+accessibility/grid-with-aria-owned-cells.html [ Skip ]
# TODO webkit-font-smoothing is not supported.
webkit.org/b/149245 imported/w3c/web-platform-tests/css/css-multicol/multicol-basic-001.html [ Skip ]
Modified: trunk/Source/WebCore/accessibility/AXObjectCache.cpp (294877 => 294878)
--- trunk/Source/WebCore/accessibility/AXObjectCache.cpp 2022-05-26 14:00:35 UTC (rev 294877)
+++ trunk/Source/WebCore/accessibility/AXObjectCache.cpp 2022-05-26 16:01:38 UTC (rev 294878)
@@ -116,7 +116,9 @@
#include "TextIterator.h"
#include <utility>
#include <wtf/DataLog.h>
+#include <wtf/NeverDestroyed.h>
#include <wtf/SetForScope.h>
+#include <wtf/text/AtomString.h>
#if COMPILER(MSVC)
// See https://msdn.microsoft.com/en-us/library/1wea5zwe.aspx
@@ -948,7 +950,7 @@
{
ASSERT(isMainThread());
- return axIDs.map([this] (AXID axID) -> RefPtr<AXCoreObject> {
+ return axIDs.map([this] (const auto& axID) -> RefPtr<AXCoreObject> {
ASSERT(axID.isValid());
return objectFromAXID(axID);
});
@@ -1839,9 +1841,9 @@
#if PLATFORM(COCOA)
// If the combobox's activeDescendant is inside a descendant owned or controlled by the combobox, that descendant should be the target of the notification and not the combobox itself.
if (object->isComboBox()) {
- if (auto* ownedObject = Accessibility::findRelatedObjectInAncestry(*object, aria_ownsAttr, *activeDescendant))
+ if (auto* ownedObject = Accessibility::findRelatedObjectInAncestry(*object, AXRelationType::OwnerFor, *activeDescendant))
target = ownedObject;
- else if (auto* controlledObject = Accessibility::findRelatedObjectInAncestry(*object, aria_controlsAttr, *activeDescendant))
+ else if (auto* controlledObject = Accessibility::findRelatedObjectInAncestry(*object, AXRelationType::ControllerFor, *activeDescendant))
target = controlledObject;
}
#endif
@@ -1889,12 +1891,15 @@
return false;
}
-
+
void AXObjectCache::handleAttributeChange(const QualifiedName& attrName, Element* element)
{
if (!shouldProcessAttributeChange(attrName, element))
return;
+ if (relationAttributes().contains(attrName))
+ relationsNeedUpdate(true);
+
if (attrName == roleAttr) {
if (auto* axObject = get(element))
axObject->updateRole();
@@ -1910,8 +1915,10 @@
#if ENABLE(ACCESSIBILITY_ISOLATED_TREE)
else if (attrName == langAttr)
updateIsolatedTree(get(element), AXObjectCache::AXLanguageChanged);
- else if (attrName == idAttr)
+ else if (attrName == idAttr) {
+ relationsNeedUpdate(true);
updateIsolatedTree(get(element), AXObjectCache::AXIdAttributeChanged);
+ }
#endif
else if (attrName == openAttr && is<HTMLDialogElement>(*element)) {
deferModalChange(element);
@@ -3430,7 +3437,6 @@
void AXObjectCache::updateIsolatedTree(const Vector<std::pair<RefPtr<AXCoreObject>, AXNotification>>& notifications)
{
AXTRACE("AXObjectCache::updateIsolatedTree"_s);
- AXLOG(*this);
if (!m_pageID) {
AXLOG("No pageID.");
@@ -3657,6 +3663,191 @@
return data;
}
+Vector<QualifiedName>& AXObjectCache::relationAttributes()
+{
+ static NeverDestroyed<Vector<QualifiedName>> relationAttributes = Vector<QualifiedName> {
+ aria_activedescendantAttr,
+ aria_controlsAttr,
+ aria_describedbyAttr,
+ aria_detailsAttr,
+ aria_errormessageAttr,
+ aria_flowtoAttr,
+ aria_labelledbyAttr,
+ aria_labeledbyAttr,
+ aria_ownsAttr,
+ headersAttr,
+ };
+ return relationAttributes;
+}
+
+AXRelationType AXObjectCache::symmetricRelation(AXRelationType relationType)
+{
+ switch (relationType) {
+ case AXRelationType::ActiveDescendant:
+ return AXRelationType::ActiveDescendantOf;
+ case AXRelationType::ActiveDescendantOf:
+ return AXRelationType::ActiveDescendant;
+ case AXRelationType::ControlledBy:
+ return AXRelationType::ControllerFor;
+ case AXRelationType::ControllerFor:
+ return AXRelationType::ControlledBy;
+ case AXRelationType::DescribedBy:
+ return AXRelationType::DescriptionFor;
+ case AXRelationType::DescriptionFor:
+ return AXRelationType::DescribedBy;
+ case AXRelationType::Details:
+ return AXRelationType::DetailsFor;
+ case AXRelationType::DetailsFor:
+ return AXRelationType::Details;
+ case AXRelationType::ErrorMessage:
+ return AXRelationType::ErrorMessageFor;
+ case AXRelationType::ErrorMessageFor:
+ return AXRelationType::ErrorMessage;
+ case AXRelationType::FlowsFrom:
+ return AXRelationType::FlowsTo;
+ case AXRelationType::FlowsTo:
+ return AXRelationType::FlowsFrom;
+ case AXRelationType::Headers:
+ return AXRelationType::HeaderFor;
+ case AXRelationType::HeaderFor:
+ return AXRelationType::Headers;
+ case AXRelationType::LabelledBy:
+ return AXRelationType::LabelFor;
+ case AXRelationType::LabelFor:
+ return AXRelationType::LabelledBy;
+ case AXRelationType::OwnedBy:
+ return AXRelationType::OwnerFor;
+ case AXRelationType::OwnerFor:
+ return AXRelationType::OwnedBy;
+ case AXRelationType::None:
+ return AXRelationType::None;
+ }
+}
+
+AXRelationType AXObjectCache::attributeToRelationType(const QualifiedName& attribute)
+{
+ if (attribute == aria_activedescendantAttr)
+ return AXRelationType::ActiveDescendant;
+ if (attribute == aria_controlsAttr)
+ return AXRelationType::ControllerFor;
+ if (attribute == aria_describedbyAttr)
+ return AXRelationType::DescribedBy;
+ if (attribute == aria_detailsAttr)
+ return AXRelationType::Details;
+ if (attribute == aria_errormessageAttr)
+ return AXRelationType::ErrorMessage;
+ if (attribute == aria_flowtoAttr)
+ return AXRelationType::FlowsTo;
+ if (attribute == aria_labelledbyAttr || attribute == aria_labeledbyAttr)
+ return AXRelationType::LabelledBy;
+ if (attribute == aria_ownsAttr)
+ return AXRelationType::OwnerFor;
+ if (attribute == headersAttr)
+ return AXRelationType::Headers;
+ return AXRelationType::None;
+}
+
+void AXObjectCache::addRelation(Element* origin, Element* target, AXRelationType relationType)
+{
+ if (!origin || !target || origin == target || relationType == AXRelationType::None) {
+ ASSERT_NOT_REACHED();
+ return;
+ }
+ addRelation(getOrCreate(origin), getOrCreate(target), relationType);
+}
+
+void AXObjectCache::addRelation(AccessibilityObject* origin, AccessibilityObject* target, AXRelationType relationType, AddingSymmetricRelation addingSymmetricRelation)
+{
+ if (!origin || !target || origin == target || relationType == AXRelationType::None)
+ return;
+
+ auto relationsIterator = m_relations.find(origin->objectID());
+ if (relationsIterator == m_relations.end()) {
+ // No relations for this object, add the first one.
+ m_relations.add(origin->objectID(), Relations { { static_cast<uint8_t>(relationType), { target->objectID() } } });
+ } else if (auto targetsIterator = relationsIterator->value.find(static_cast<uint8_t>(relationType)); targetsIterator == relationsIterator->value.end()) {
+ // No relation of this type for this object, add the first one.
+ relationsIterator->value.add(static_cast<uint8_t>(relationType), Vector<AXID> { target->objectID() });
+ } else {
+ // There are already relations of this type for the object. Add the new relation.
+ if (relationType == AXRelationType::ActiveDescendant
+ || relationType == AXRelationType::OwnedBy) {
+ // There should be only one active descendant and only one owner. Enforce that by removing any existing targets.
+ targetsIterator->value.clear();
+ }
+ targetsIterator->value.append(target->objectID());
+ }
+
+ if (addingSymmetricRelation == AddingSymmetricRelation::No) {
+ if (auto symmetric = symmetricRelation(relationType); symmetric != AXRelationType::None)
+ addRelation(target, origin, symmetric, AddingSymmetricRelation::Yes);
+ }
+}
+
+void AXObjectCache::updateRelationsIfNeeded()
+{
+ AXTRACE("AXObjectCache::updateRelationsIfNeeded"_s);
+
+ if (!m_relationsNeedUpdate)
+ return;
+ relationsNeedUpdate(false);
+ AXLOG("Updating relations.");
+ m_relations.clear();
+
+ struct RelationOrigin {
+ Element* originElement { nullptr };
+ AtomString targetID;
+ AXRelationType relationType;
+ };
+
+ struct RelationTarget {
+ Element* targetElement { nullptr };
+ AtomString targetID;
+ };
+
+ Vector<RelationOrigin> origins;
+ Vector<RelationTarget> targets;
+ for (auto& element : descendantsOfType<Element>(m_document.rootNode())) {
+ // Collect all possible origins, i.e., elements with non-empty relation attributes.
+ for (const auto& attribute : relationAttributes()) {
+ auto& idsString = element.attributeWithoutSynchronization(attribute);
+ SpaceSplitString ids(idsString, SpaceSplitString::ShouldFoldCase::No);
+ for (size_t i = 0; i < ids.size(); ++i)
+ origins.append({ &element, ids[i], attributeToRelationType(attribute) });
+ }
+
+ // Collect all possible targets, i.e., elements with a non-empty id attribute.
+ auto elementID = element.attributeWithoutSynchronization(idAttr);
+ if (!elementID.isEmpty())
+ targets.append({ &element, elementID });
+ }
+
+ for (const auto& origin : origins) {
+ for (const auto& target : targets) {
+ if (origin.originElement == target.targetElement) {
+ // Relationship should be between different elements.
+ continue;
+ }
+
+ if (origin.targetID == target.targetID)
+ addRelation(origin.originElement, target.targetElement, origin.relationType);
+ }
+ }
+}
+
+std::optional<Vector<AXID>> AXObjectCache::relatedObjectsFor(const AXCoreObject& object, AXRelationType relationType)
+{
+ updateRelationsIfNeeded();
+ auto relationsIterator = m_relations.find(object.objectID());
+ if (relationsIterator == m_relations.end())
+ return std::nullopt;
+
+ auto targetsIterator = relationsIterator->value.find(static_cast<uint8_t>(relationType));
+ if (targetsIterator == relationsIterator->value.end())
+ return std::nullopt;
+ return targetsIterator->value;
+}
+
AXAttributeCacheEnabler::AXAttributeCacheEnabler(AXObjectCache* cache)
: m_cache(cache)
{
Modified: trunk/Source/WebCore/accessibility/AXObjectCache.h (294877 => 294878)
--- trunk/Source/WebCore/accessibility/AXObjectCache.h 2022-05-26 14:00:35 UTC (rev 294877)
+++ trunk/Source/WebCore/accessibility/AXObjectCache.h 2022-05-26 16:01:38 UTC (rev 294878)
@@ -377,6 +377,9 @@
AXTreeData treeData();
+ // Returns the IDs of the objects that relate to the given object with the specified relationship.
+ std::optional<Vector<AXID>> relatedObjectsFor(const AXCoreObject&, AXRelationType);
+
#if ENABLE(ACCESSIBILITY_ISOLATED_TREE)
WEBCORE_EXPORT static bool isIsolatedTreeEnabled();
WEBCORE_EXPORT static bool usedOnAXThread();
@@ -487,6 +490,16 @@
void handleModalChange(Element&);
bool modalElementHasAccessibleContent(Element&);
+ // Relationships between objects.
+ static Vector<QualifiedName>& relationAttributes();
+ static AXRelationType attributeToRelationType(const QualifiedName&);
+ enum class AddingSymmetricRelation : bool { No, Yes };
+ static AXRelationType symmetricRelation(AXRelationType);
+ void addRelation(Element*, Element*, AXRelationType);
+ void addRelation(AccessibilityObject*, AccessibilityObject*, AXRelationType, AddingSymmetricRelation = AddingSymmetricRelation::No);
+ void updateRelationsIfNeeded();
+ void relationsNeedUpdate(bool needUpdate) { m_relationsNeedUpdate = needUpdate; }
+
Document& m_document;
const std::optional<PageIdentifier> m_pageID; // constant for object's lifetime.
HashMap<AXID, RefPtr<AccessibilityObject>> m_objects;
@@ -542,6 +555,11 @@
bool m_performingDeferredCacheUpdate { false };
double m_loadingProgress { 0 };
+ // Relationships between objects.
+ using Relations = HashMap<AXRelationType, Vector<AXID>, DefaultHash<uint8_t>, WTF::UnsignedWithZeroKeyHashTraits<uint8_t>>;
+ HashMap<AXID, Relations> m_relations;
+ bool m_relationsNeedUpdate { true };
+
#if USE(ATSPI)
ListHashSet<RefPtr<AXCoreObject>> m_deferredParentChangedList;
#endif
Modified: trunk/Source/WebCore/accessibility/AccessibilityObject.cpp (294877 => 294878)
--- trunk/Source/WebCore/accessibility/AccessibilityObject.cpp 2022-05-26 14:00:35 UTC (rev 294877)
+++ trunk/Source/WebCore/accessibility/AccessibilityObject.cpp 2022-05-26 16:01:38 UTC (rev 294878)
@@ -3922,38 +3922,16 @@
return nullptr;
}
-AXCoreObject::AccessibilityChildrenVector AccessibilityObject::ariaElementsFromAttribute(const QualifiedName& attribute) const
+AXCoreObject::AccessibilityChildrenVector AccessibilityObject::relatedObjects(AXRelationType relationType) const
{
auto* cache = axObjectCache();
if (!cache)
return { };
- return elementsFromAttribute(attribute).map([cache] (auto* element) -> RefPtr<AXCoreObject> {
- return cache->getOrCreate(element);
- });
-}
-// FIXME: This function iterates the whole DOM tree and tries to match every Element in the tree, which is very expensive.
-// We should find a better way to achieve this.
-AXCoreObject::AccessibilityChildrenVector AccessibilityObject::ariaElementsReferencedByAttribute(const QualifiedName& attribute) const
-{
- auto id = identifierAttribute();
- if (id.isEmpty())
+ auto relatedObjectIDs = cache->relatedObjectsFor(*this, relationType);
+ if (!relatedObjectIDs)
return { };
-
- auto* cache = axObjectCache();
- if (!cache)
- return { };
-
- AccessibilityChildrenVector objects;
- for (auto& element : descendantsOfType<Element>(node()->treeScope().rootNode())) {
- auto& idList = element.attributeWithoutSynchronization(attribute);
- if (!SpaceSplitString::spaceSplitStringContainsValue(idList, id, SpaceSplitString::ShouldFoldCase::No))
- continue;
-
- if (auto* object = cache->getOrCreate(&element))
- objects.append(object);
- }
- return objects;
+ return cache->objectsForIDs(*relatedObjectIDs);
}
bool AccessibilityObject::isActiveDescendantOfFocusedContainer() const
@@ -3969,83 +3947,77 @@
AXCoreObject::AccessibilityChildrenVector AccessibilityObject::activeDescendantOfObjects() const
{
- return ariaElementsReferencedByAttribute(aria_activedescendantAttr);
+ return relatedObjects(AXRelationType::ActiveDescendantOf);
}
AXCoreObject::AccessibilityChildrenVector AccessibilityObject::controlledObjects() const
{
- return ariaElementsFromAttribute(aria_controlsAttr);
+ return relatedObjects(AXRelationType::ControllerFor);
}
AXCoreObject::AccessibilityChildrenVector AccessibilityObject::controllers() const
{
- return ariaElementsReferencedByAttribute(aria_controlsAttr);
+ return relatedObjects(AXRelationType::ControlledBy);
}
AXCoreObject::AccessibilityChildrenVector AccessibilityObject::describedByObjects() const
{
- return ariaElementsFromAttribute(aria_describedbyAttr);
+ return relatedObjects(AXRelationType::DescribedBy);
}
AXCoreObject::AccessibilityChildrenVector AccessibilityObject::descriptionForObjects() const
{
- return ariaElementsReferencedByAttribute(aria_describedbyAttr);
+ return relatedObjects(AXRelationType::DescriptionFor);
}
AXCoreObject::AccessibilityChildrenVector AccessibilityObject::detailedByObjects() const
{
- return ariaElementsFromAttribute(aria_detailsAttr);
+ return relatedObjects(AXRelationType::Details);
}
AXCoreObject::AccessibilityChildrenVector AccessibilityObject::detailsForObjects() const
{
- return ariaElementsReferencedByAttribute(aria_detailsAttr);
+ return relatedObjects(AXRelationType::DetailsFor);
}
AXCoreObject::AccessibilityChildrenVector AccessibilityObject::errorMessageObjects() const
{
- return ariaElementsFromAttribute(aria_errormessageAttr);
+ return relatedObjects(AXRelationType::ErrorMessage);
}
AXCoreObject::AccessibilityChildrenVector AccessibilityObject::errorMessageForObjects() const
{
- return ariaElementsReferencedByAttribute(aria_errormessageAttr);
+ return relatedObjects(AXRelationType::ErrorMessageFor);
}
AXCoreObject::AccessibilityChildrenVector AccessibilityObject::flowToObjects() const
{
- return ariaElementsFromAttribute(aria_flowtoAttr);
+ return relatedObjects(AXRelationType::FlowsTo);
}
AXCoreObject::AccessibilityChildrenVector AccessibilityObject::flowFromObjects() const
{
- return ariaElementsReferencedByAttribute(aria_flowtoAttr);
+ return relatedObjects(AXRelationType::FlowsFrom);
}
AXCoreObject::AccessibilityChildrenVector AccessibilityObject::labelledByObjects() const
{
- auto labelledByObjects = ariaElementsFromAttribute(aria_labelledbyAttr);
- if (labelledByObjects.isEmpty())
- labelledByObjects = ariaElementsFromAttribute(aria_labeledbyAttr);
- return labelledByObjects;
+ return relatedObjects(AXRelationType::LabelledBy);
}
AXCoreObject::AccessibilityChildrenVector AccessibilityObject::labelForObjects() const
{
- auto objects = ariaElementsReferencedByAttribute(aria_labelledbyAttr);
- if (objects.isEmpty())
- objects = ariaElementsReferencedByAttribute(aria_labeledbyAttr);
- return objects;
+ return relatedObjects(AXRelationType::LabelFor);
}
AXCoreObject::AccessibilityChildrenVector AccessibilityObject::ownedObjects() const
{
- return ariaElementsFromAttribute(aria_ownsAttr);
+ return relatedObjects(AXRelationType::OwnerFor);
}
AXCoreObject::AccessibilityChildrenVector AccessibilityObject::owners() const
{
- return ariaElementsReferencedByAttribute(aria_ownsAttr);
+ return relatedObjects(AXRelationType::OwnedBy);
}
void AccessibilityObject::setIsIgnoredFromParentDataForChild(AXCoreObject* child)
Modified: trunk/Source/WebCore/accessibility/AccessibilityObject.h (294877 => 294878)
--- trunk/Source/WebCore/accessibility/AccessibilityObject.h 2022-05-26 14:00:35 UTC (rev 294877)
+++ trunk/Source/WebCore/accessibility/AccessibilityObject.h 2022-05-26 16:01:38 UTC (rev 294878)
@@ -785,8 +785,7 @@
String documentEncoding() const override;
AccessibilityChildrenVector documentLinks() override { return AccessibilityChildrenVector(); }
- AccessibilityChildrenVector ariaElementsFromAttribute(const QualifiedName&) const;
- AccessibilityChildrenVector ariaElementsReferencedByAttribute(const QualifiedName&) const;
+ AccessibilityChildrenVector relatedObjects(AXRelationType) const;
protected:
AccessibilityObject() = default;
Modified: trunk/Source/WebCore/accessibility/AccessibilityObjectInterface.h (294877 => 294878)
--- trunk/Source/WebCore/accessibility/AccessibilityObjectInterface.h 2022-05-26 14:00:35 UTC (rev 294877)
+++ trunk/Source/WebCore/accessibility/AccessibilityObjectInterface.h 2022-05-26 16:01:38 UTC (rev 294878)
@@ -776,6 +776,29 @@
enum class AccessibilityMathScriptObjectType { Subscript, Superscript };
enum class AccessibilityMathMultiscriptObjectType { PreSubscript, PreSuperscript, PostSubscript, PostSuperscript };
+// Relationships between AX objects.
+enum class AXRelationType : uint8_t {
+ None,
+ ActiveDescendant,
+ ActiveDescendantOf,
+ ControlledBy,
+ ControllerFor,
+ DescribedBy,
+ DescriptionFor,
+ Details,
+ DetailsFor,
+ ErrorMessage,
+ ErrorMessageFor,
+ FlowsFrom,
+ FlowsTo,
+ Headers,
+ HeaderFor,
+ LabelledBy,
+ LabelFor,
+ OwnedBy,
+ OwnerFor,
+};
+
// Use this struct to store the isIgnored data that depends on the parents, so that in addChildren()
// we avoid going up the parent chain for each element while traversing the tree with useful information already.
struct AccessibilityIsIgnoredFromParentData {
@@ -1617,9 +1640,9 @@
}
template<typename T>
-T* findRelatedObjectInAncestry(const T& object, const QualifiedName& relationAttribute, const T& descendant)
+T* findRelatedObjectInAncestry(const T& object, AXRelationType relationType, const T& descendant)
{
- auto relatedObjects = object.ariaElementsFromAttribute(relationAttribute);
+ auto relatedObjects = object.relatedObjects(relationType);
for (const auto& object : relatedObjects) {
auto* ancestor = findAncestor(descendant, false, [&object] (const auto& ancestor) {
return object.get() == &ancestor;
Modified: trunk/Source/WebCore/accessibility/AccessibilityRenderObject.cpp (294877 => 294878)
--- trunk/Source/WebCore/accessibility/AccessibilityRenderObject.cpp 2022-05-26 14:00:35 UTC (rev 294877)
+++ trunk/Source/WebCore/accessibility/AccessibilityRenderObject.cpp 2022-05-26 16:01:38 UTC (rev 294878)
@@ -2681,7 +2681,7 @@
AccessibilityObject* AccessibilityRenderObject::activeDescendant() const
{
- auto activeDescendants = ariaElementsFromAttribute(aria_activedescendantAttr);
+ auto activeDescendants = relatedObjects(AXRelationType::ActiveDescendant);
ASSERT(activeDescendants.size() <= 1);
if (!activeDescendants.isEmpty())
return downcast<AccessibilityObject>(activeDescendants[0].get());
Modified: trunk/Source/WebCore/accessibility/AccessibilityTableCell.cpp (294877 => 294878)
--- trunk/Source/WebCore/accessibility/AccessibilityTableCell.cpp 2022-05-26 14:00:35 UTC (rev 294877)
+++ trunk/Source/WebCore/accessibility/AccessibilityTableCell.cpp 2022-05-26 16:01:38 UTC (rev 294878)
@@ -244,7 +244,7 @@
return { };
// Choose columnHeaders as the place where the "headers" attribute is reported.
- auto headers = ariaElementsFromAttribute(headersAttr);
+ auto headers = relatedObjects(AXRelationType::Headers);
// If the headers attribute returned valid values, then do not further search for column headers.
if (!headers.isEmpty())
return headers;
Modified: trunk/Source/WebCore/accessibility/atspi/AccessibilityObjectAtspi.cpp (294877 => 294878)
--- trunk/Source/WebCore/accessibility/atspi/AccessibilityObjectAtspi.cpp 2022-05-26 14:00:35 UTC (rev 294877)
+++ trunk/Source/WebCore/accessibility/atspi/AccessibilityObjectAtspi.cpp 2022-05-26 16:01:38 UTC (rev 294878)
@@ -1049,7 +1049,6 @@
}
if (!wrappers.isEmpty())
map.add(relation, WTFMove(wrappers));
-
};
AccessibilityObject::AccessibilityChildrenVector ariaLabelledByElements;
Modified: trunk/Source/WebCore/accessibility/mac/WebAccessibilityObjectWrapperMac.mm (294877 => 294878)
--- trunk/Source/WebCore/accessibility/mac/WebAccessibilityObjectWrapperMac.mm 2022-05-26 14:00:35 UTC (rev 294877)
+++ trunk/Source/WebCore/accessibility/mac/WebAccessibilityObjectWrapperMac.mm 2022-05-26 16:01:38 UTC (rev 294878)
@@ -1932,7 +1932,6 @@
return [self computedRoleString];
if ([attributeName isEqualToString: NSAccessibilityParentAttribute]) {
-
// This will return the parent of the AXWebArea, if this is a web area.
id scrollViewParent = [self scrollViewParent];
if (scrollViewParent)
@@ -4138,6 +4137,8 @@
ALLOW_DEPRECATED_DECLARATIONS_BEGIN
- (NSUInteger)accessibilityArrayAttributeCount:(NSString *)attribute
{
+ AXTRACE(makeString("WebAccessibilityObjectWrapper accessibilityArrayAttributeCount:", String(attribute)));
+
auto* backingObject = self.updateObjectBackingStore;
if (!backingObject)
return 0;