From 0b61863f7ec76afa5d9f201579507012fe16de4a Mon Sep 17 00:00:00 2001 From: Tyler Wilcock Date: Mon, 17 Apr 2023 00:02:09 -0700 Subject: [PATCH] AX: isSelected AX APIs don't work for some types of display:contents elements https://bugs.webkit.org/show_bug.cgi?id=255480 rdar://problem/108083208 Reviewed by Chris Fleizach. This happens because `isSelected` is implemented for AccessibilityRenderObject and not AccessibilityNodeObject despite the fact that the implementation does not actually require a renderer. This patch moves the implementation to AccessibilityObject since all the required components of this logic are available on AXCoreObject. This patch also fixes AXCoreObject::textLength for display:contents elements. * LayoutTests/accessibility/aria-selected.html: Add a display:contents testcase. * LayoutTests/platform/glib/accessibility/aria-selected-expected.txt: * LayoutTests/platform/mac/accessibility/aria-selected-expected.txt: * LayoutTests/platform/wincairo/accessibility/aria-selected-expected.txt: * Source/WebCore/accessibility/AccessibilityObject.cpp: (WebCore::AccessibilityObject::isSelected const): (WebCore::AccessibilityObject::isTabItemSelected const): (WebCore::AccessibilityObject::textLength const): * Source/WebCore/accessibility/AccessibilityObject.h: * Source/WebCore/accessibility/AccessibilityRenderObject.cpp: (WebCore::AccessibilityRenderObject::textLength const): Deleted. (WebCore::AccessibilityRenderObject::isSelected const): Deleted. (WebCore::AccessibilityRenderObject::isTabItemSelected const): Deleted. * Source/WebCore/accessibility/AccessibilityRenderObject.h: Canonical link: https://commits.webkit.org/263014@main --- LayoutTests/accessibility/aria-selected.html | 6 ++ .../accessibility/aria-selected-expected.txt | 2 + .../accessibility/aria-selected-expected.txt | 2 + .../accessibility/aria-selected-expected.txt | 1 + .../accessibility/AccessibilityObject.cpp | 63 +++++++++++++++++ .../accessibility/AccessibilityObject.h | 5 +- .../AccessibilityRenderObject.cpp | 67 ------------------- .../accessibility/AccessibilityRenderObject.h | 3 - 8 files changed, 77 insertions(+), 72 deletions(-) diff --git a/LayoutTests/accessibility/aria-selected.html b/LayoutTests/accessibility/aria-selected.html index 91e7b03411d8..7cf4786d43bc 100644 --- a/LayoutTests/accessibility/aria-selected.html +++ b/LayoutTests/accessibility/aria-selected.html @@ -11,6 +11,11 @@
2
3
+
+
1
+
2
+
3
+
1
2
@@ -59,6 +64,7 @@ description("This tests that items with aria-selected are reported as selected children of the parent container."); if (window.accessibilityController) { selectedChildInfo(window.accessibilityController.accessibleElementById("tablist")); + selectedChildInfo(window.accessibilityController.accessibleElementById("tablist-with-display-contents")); selectedChildInfo(window.accessibilityController.accessibleElementById("tree")); selectedChildInfo(window.accessibilityController.accessibleElementById("grid")); selectedChildInfo(window.accessibilityController.accessibleElementById("treegrid")); diff --git a/LayoutTests/platform/glib/accessibility/aria-selected-expected.txt b/LayoutTests/platform/glib/accessibility/aria-selected-expected.txt index a4d356731609..c757833b56f5 100644 --- a/LayoutTests/platform/glib/accessibility/aria-selected-expected.txt +++ b/LayoutTests/platform/glib/accessibility/aria-selected-expected.txt @@ -3,6 +3,8 @@ This tests that items with aria-selected are reported as selected children of th On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". +AXRole: AXTabGroup has 1 selected child(ren) + second (AXRole: AXTab) isSelectable: true isSelected: true AXRole: AXTabGroup has 1 selected child(ren) second (AXRole: AXTab) isSelectable: true isSelected: true AXRole: AXTree has 2 selected child(ren) diff --git a/LayoutTests/platform/mac/accessibility/aria-selected-expected.txt b/LayoutTests/platform/mac/accessibility/aria-selected-expected.txt index df430dfa0f4c..168abb15df14 100644 --- a/LayoutTests/platform/mac/accessibility/aria-selected-expected.txt +++ b/LayoutTests/platform/mac/accessibility/aria-selected-expected.txt @@ -3,6 +3,8 @@ This tests that items with aria-selected are reported as selected children of th On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". +AXRole: AXTabGroup has 1 selected child(ren) + second (AXRole: AXRadioButton) isSelectable: true isSelected: true AXRole: AXTabGroup has 1 selected child(ren) second (AXRole: AXRadioButton) isSelectable: true isSelected: true AXRole: AXOutline has 2 selected child(ren) diff --git a/LayoutTests/platform/wincairo/accessibility/aria-selected-expected.txt b/LayoutTests/platform/wincairo/accessibility/aria-selected-expected.txt index 474d1bd6c2bb..09583c5a8a22 100644 --- a/LayoutTests/platform/wincairo/accessibility/aria-selected-expected.txt +++ b/LayoutTests/platform/wincairo/accessibility/aria-selected-expected.txt @@ -3,6 +3,7 @@ This tests that items with aria-selected are reported as selected children of th On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". +AXRole: AXTabGroup has 0 selected child(ren) AXRole: AXTabGroup has 0 selected child(ren) AXRole: outline has 0 selected child(ren) AXRole: AXTable has 0 selected child(ren) diff --git a/Source/WebCore/accessibility/AccessibilityObject.cpp b/Source/WebCore/accessibility/AccessibilityObject.cpp index 4be06d6c1268..fa508302fa14 100644 --- a/Source/WebCore/accessibility/AccessibilityObject.cpp +++ b/Source/WebCore/accessibility/AccessibilityObject.cpp @@ -2762,6 +2762,69 @@ AutoFillButtonType AccessibilityObject::valueAutofillButtonType() const return downcast(*this->node()).autoFillButtonType(); } +bool AccessibilityObject::isSelected() const +{ + if (!renderer() && !node()) + return false; + + if (equalLettersIgnoringASCIICase(getAttribute(aria_selectedAttr), "true"_s)) + return true; + + if (isTabItem() && isTabItemSelected()) + return true; + + // Menu items are considered selectable by assistive technologies + if (isMenuItem()) + return isFocused() || parentObjectUnignored()->activeDescendant() == this; + + return false; +} + +bool AccessibilityObject::isTabItemSelected() const +{ + if (!isTabItem() || (!renderer() && !node())) + return false; + + WeakPtr node = this->node(); + if (!node || !node->isElementNode()) + return false; + + // The ARIA spec says a tab item can also be selected if it is aria-labeled by a tabpanel + // that has keyboard focus inside of it, or if a tabpanel in its aria-controls list has KB + // focus inside of it. + auto* focusedElement = static_cast(focusedUIElement()); + if (!focusedElement) + return false; + + auto* cache = axObjectCache(); + if (!cache) + return false; + + auto elements = elementsFromAttribute(aria_controlsAttr); + for (const auto& element : elements) { + auto* tabPanel = cache->getOrCreate(element); + + // A tab item should only control tab panels. + if (!tabPanel || tabPanel->roleValue() != AccessibilityRole::TabPanel) + continue; + + auto* checkFocusElement = focusedElement; + // Check if the focused element is a descendant of the element controlled by the tab item. + while (checkFocusElement) { + if (tabPanel == checkFocusElement) + return true; + checkFocusElement = checkFocusElement->parentObject(); + } + } + return false; +} + +unsigned AccessibilityObject::textLength() const +{ + ASSERT(isTextControl()); + return text().length(); +} + std::optional AccessibilityObject::textContent() const { if (!hasTextContent()) diff --git a/Source/WebCore/accessibility/AccessibilityObject.h b/Source/WebCore/accessibility/AccessibilityObject.h index e3af20ab4fb7..b6d5d52938a2 100644 --- a/Source/WebCore/accessibility/AccessibilityObject.h +++ b/Source/WebCore/accessibility/AccessibilityObject.h @@ -207,7 +207,8 @@ class AccessibilityObject : public AXCoreObject, public CanMakeWeakPtr(*m_renderer).frameView().layoutContext().layoutCount(); } -unsigned AccessibilityRenderObject::textLength() const -{ - ASSERT(isTextControl()); - return text().length(); -} - PlainTextRange AccessibilityRenderObject::documentBasedSelectedTextRange() const { auto selectedVisiblePositionRange = this->selectedVisiblePositionRange(); @@ -1724,67 +1718,6 @@ bool AccessibilityRenderObject::isVisited() const return m_renderer->style().isLink() && m_renderer->style().insideLink() == InsideLink::InsideVisited; } -bool AccessibilityRenderObject::isSelected() const -{ - if (!m_renderer) - return false; - - if (!m_renderer->node()) - return false; - - if (equalLettersIgnoringASCIICase(getAttribute(aria_selectedAttr), "true"_s)) - return true; - - if (isTabItem() && isTabItemSelected()) - return true; - - // Menu items are considered selectable by assistive technologies - if (isMenuItem()) - return isFocused() || parentObjectUnignored()->activeDescendant() == this; - - return false; -} - -bool AccessibilityRenderObject::isTabItemSelected() const -{ - if (!isTabItem() || !m_renderer) - return false; - - Node* node = m_renderer->node(); - if (!node || !node->isElementNode()) - return false; - - // The ARIA spec says a tab item can also be selected if it is aria-labeled by a tabpanel - // that has keyboard focus inside of it, or if a tabpanel in its aria-controls list has KB - // focus inside of it. - auto* focusedElement = static_cast(focusedUIElement()); - if (!focusedElement) - return false; - - auto* cache = axObjectCache(); - if (!cache) - return false; - - auto elements = elementsFromAttribute(aria_controlsAttr); - for (const auto& element : elements) { - auto* tabPanel = cache->getOrCreate(element); - - // A tab item should only control tab panels. - if (!tabPanel || tabPanel->roleValue() != AccessibilityRole::TabPanel) - continue; - - auto* checkFocusElement = focusedElement; - // Check if the focused element is a descendant of the element controlled by the tab item. - while (checkFocusElement) { - if (tabPanel == checkFocusElement) - return true; - checkFocusElement = checkFocusElement->parentObject(); - } - } - - return false; -} - void AccessibilityRenderObject::setSelectedRows(AccessibilityChildrenVector& selectedRows) { // Setting selected only makes sense in trees and tables (and tree-tables). diff --git a/Source/WebCore/accessibility/AccessibilityRenderObject.h b/Source/WebCore/accessibility/AccessibilityRenderObject.h index 359d9b297023..7a0159af9954 100644 --- a/Source/WebCore/accessibility/AccessibilityRenderObject.h +++ b/Source/WebCore/accessibility/AccessibilityRenderObject.h @@ -57,7 +57,6 @@ class AccessibilityRenderObject : public AccessibilityNodeObject { virtual ~AccessibilityRenderObject(); bool isAttachment() const override; - bool isSelected() const override; bool isOffScreen() const override; bool isUnvisited() const override; bool isVisited() const override; @@ -117,7 +116,6 @@ class AccessibilityRenderObject : public AccessibilityNodeObject { String stringValue() const override; String helpText() const override; String textUnderElement(AccessibilityTextUnderElementMode = AccessibilityTextUnderElementMode()) const override; - unsigned textLength() const override; String selectedText() const override; String accessKey() const override; @@ -203,7 +201,6 @@ class AccessibilityRenderObject : public AccessibilityNodeObject { bool nodeIsTextControl(const Node*) const; Path elementPath() const override; - bool isTabItemSelected() const; LayoutRect checkboxOrRadioRect() const; void addRadioButtonGroupMembers(AccessibilityChildrenVector& linkedUIElements) const; void addRadioButtonGroupChildren(AXCoreObject*, AccessibilityChildrenVector&) const;