Skip to content

Commit

Permalink
AX: isSelected AX APIs don't work for some types of display:contents …
Browse files Browse the repository at this point in the history
…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
  • Loading branch information
twilco committed Apr 17, 2023
1 parent c778044 commit 0b61863
Show file tree
Hide file tree
Showing 8 changed files with 77 additions and 72 deletions.
6 changes: 6 additions & 0 deletions LayoutTests/accessibility/aria-selected.html
Expand Up @@ -11,6 +11,11 @@
<div role="tab" aria-label="second" aria-selected="true">2</div>
<div role="tab" aria-label="third">3</div>
</div>
<div role="tablist" id="tablist-with-display-contents" tabindex="0">
<div role="tab" aria-label="first">1</div>
<div role="tab" aria-label="second" aria-selected="true" style="display:contents">2</div>
<div role="tab" aria-label="third">3</div>
</div>
<div role="tree" id="tree" aria-multiselectable="true" tabindex="0">
<div role="treeitem" aria-label="first" aria-selected="true">1</div>
<div role="treeitem" aria-label="second">2</div>
Expand Down Expand Up @@ -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"));
Expand Down
Expand Up @@ -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)
Expand Down
Expand Up @@ -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)
Expand Down
Expand Up @@ -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)
Expand Down
63 changes: 63 additions & 0 deletions Source/WebCore/accessibility/AccessibilityObject.cpp
Expand Up @@ -2762,6 +2762,69 @@ AutoFillButtonType AccessibilityObject::valueAutofillButtonType() const
return downcast<HTMLInputElement>(*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<AccessibilityObject*>(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<String> AccessibilityObject::textContent() const
{
if (!hasTextContent())
Expand Down
5 changes: 3 additions & 2 deletions Source/WebCore/accessibility/AccessibilityObject.h
Expand Up @@ -207,7 +207,8 @@ class AccessibilityObject : public AXCoreObject, public CanMakeWeakPtr<Accessibi

bool isChecked() const override { return false; }
bool isEnabled() const override { return false; }
bool isSelected() const override { return false; }
bool isSelected() const override;
bool isTabItemSelected() const;
bool isFocused() const override { return false; }
virtual bool isHovered() const { return false; }
bool isIndeterminate() const override { return false; }
Expand Down Expand Up @@ -388,7 +389,7 @@ class AccessibilityObject : public AXCoreObject, public CanMakeWeakPtr<Accessibi
String stringValue() const override { return { }; }
String textUnderElement(AccessibilityTextUnderElementMode = AccessibilityTextUnderElementMode()) const override { return { }; }
String text() const override { return { }; }
unsigned textLength() const override { return 0; }
unsigned textLength() const final;
#if PLATFORM(COCOA)
// Returns an array of strings and AXObject wrappers corresponding to the
// textruns and replacement nodes included in the given range.
Expand Down
67 changes: 0 additions & 67 deletions Source/WebCore/accessibility/AccessibilityRenderObject.cpp
Expand Up @@ -1541,12 +1541,6 @@ int AccessibilityRenderObject::layoutCount() const
return downcast<RenderView>(*m_renderer).frameView().layoutContext().layoutCount();
}

unsigned AccessibilityRenderObject::textLength() const
{
ASSERT(isTextControl());
return text().length();
}

PlainTextRange AccessibilityRenderObject::documentBasedSelectedTextRange() const
{
auto selectedVisiblePositionRange = this->selectedVisiblePositionRange();
Expand Down Expand Up @@ -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<AccessibilityObject*>(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).
Expand Down
3 changes: 0 additions & 3 deletions Source/WebCore/accessibility/AccessibilityRenderObject.h
Expand Up @@ -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;
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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;
Expand Down

0 comments on commit 0b61863

Please sign in to comment.