Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
AX: VoiceOver word echo doesn't work on text inputs with a combobox role
https://bugs.webkit.org/show_bug.cgi?id=259811
rdar://112488137

Reviewed by Chris Fleizach.

This happens because prior to this patch, we didn't expose the text
entry trait for comboboxes, despite comboboxes generally (but not
always) being a combination of a text input, plus a listbox of possible
options.

With this patch, when a text input has a role of combobox, we expose the
text entry trait.

* LayoutTests/accessibility/ios-simulator/combobox-is-editable-expected.txt: Added.
* LayoutTests/accessibility/ios-simulator/combobox-is-editable.html: Added.
* Source/WebCore/accessibility/ios/WebAccessibilityObjectWrapperIOS.mm:
(-[WebAccessibilityObjectWrapper accessibilityTraits]):
* Tools/DumpRenderTree/AccessibilityUIElement.cpp:
(hasTextEntryTraitCallback):
(AccessibilityUIElement::getJSClass):
* Tools/DumpRenderTree/AccessibilityUIElement.h:
* Tools/DumpRenderTree/ios/AccessibilityUIElementIOS.mm:
(AccessibilityUIElement::hasTextEntryTrait):
* Tools/WebKitTestRunner/InjectedBundle/AccessibilityUIElement.cpp:
(WTR::AccessibilityUIElement::hasTextEntryTrait):
* Tools/WebKitTestRunner/InjectedBundle/AccessibilityUIElement.h:
* Tools/WebKitTestRunner/InjectedBundle/Bindings/AccessibilityUIElement.idl:
* Tools/WebKitTestRunner/InjectedBundle/ios/AccessibilityUIElementIOS.mm:
(WTR::AccessibilityUIElement::hasTextEntryTrait):

Canonical link: https://commits.webkit.org/266569@main
  • Loading branch information
twilco committed Aug 4, 2023
1 parent a069e94 commit 31a6df5
Show file tree
Hide file tree
Showing 10 changed files with 79 additions and 0 deletions.
@@ -0,0 +1,11 @@
This test ensures that the text entry trait is exposed for text controls with a combobox role.

PASS: accessibilityController.accessibleElementById('textarea').hasTextEntryTrait === true
PASS: accessibilityController.accessibleElementById('text-input').hasTextEntryTrait === true
PASS: accessibilityController.accessibleElementById('submit-input').hasTextEntryTrait === false
PASS: accessibilityController.accessibleElementById('select').hasTextEntryTrait === false

PASS successfullyParsed is true

TEST COMPLETE

36 changes: 36 additions & 0 deletions LayoutTests/accessibility/ios-simulator/text-input-combobox.html
@@ -0,0 +1,36 @@
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<html>
<head>
<script src="../../resources/accessibility-helper.js"></script>
<script src="../../resources/js-test.js"></script>
</head>
<body>

<!-- Some websites implement searchboxes this way. Without the proper traits,
things like VoiceOver word echo will not work. -->
<textarea role="combobox" id="textarea"></textarea>
<input type="text" role="combobox" id="text-input"></input>
<input type="submit" role="combobox" id="submit-input"></input>
<select name="pets" role="combobox" id="select">
<option value="">--Please choose an option--</option>
<option value="dog">Dog</option>
<option value="cat">Cat</option>
</select>

<script>
var output = "This test ensures that the text entry trait is exposed for text controls with a combobox role.\n\n";

if (window.accessibilityController) {
output += expect("accessibilityController.accessibleElementById('textarea').hasTextEntryTrait", "true");
output += expect("accessibilityController.accessibleElementById('text-input').hasTextEntryTrait", "true");
// Non-text inputs with a role of combobox should not have the text entry trait because they are not "editable" in
// the same way a text input is.
output += expect("accessibilityController.accessibleElementById('submit-input').hasTextEntryTrait", "false");
output += expect("accessibilityController.accessibleElementById('select').hasTextEntryTrait", "false");

debug(output);
}
</script>
</body>
</html>

Expand Up @@ -42,6 +42,7 @@
#import "HTMLFrameOwnerElement.h"
#import "HTMLInputElement.h"
#import "HTMLNames.h"
#import "HTMLTextAreaElement.h"
#import "IntRect.h"
#import "LocalFrame.h"
#import "LocalizedStrings.h"
Expand Down Expand Up @@ -840,6 +841,13 @@ - (uint64_t)accessibilityTraits
if (self.axBackingObject->isVisited())
traits |= [self _axVisitedTrait];
break;
case AccessibilityRole::ComboBox: {
auto* node = self.axBackingObject->node();
auto* inputElement = dynamicDowncast<HTMLInputElement>(node);
if ((inputElement && inputElement->isTextField()) || is<HTMLTextAreaElement>(node))
traits |= [self _accessibilityTextEntryTraits];
break;
}
case AccessibilityRole::TextField:
case AccessibilityRole::SearchField:
case AccessibilityRole::TextArea:
Expand Down
6 changes: 6 additions & 0 deletions Tools/DumpRenderTree/AccessibilityUIElement.cpp
Expand Up @@ -1640,6 +1640,11 @@ static JSValueRef hasContainedByFieldsetTraitCallback(JSContextRef context, JSOb
return JSValueMakeBoolean(context, toAXElement(thisObject)->hasContainedByFieldsetTrait());
}

static JSValueRef hasTextEntryTraitCallback(JSContextRef context, JSObjectRef thisObject, JSStringRef, JSValueRef*)
{
return JSValueMakeBoolean(context, toAXElement(thisObject)->hasTextEntryTrait());
}

static JSValueRef textMarkerRangeMatchesTextNearMarkersCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
{
JSStringRef searchText = nullptr;
Expand Down Expand Up @@ -2051,6 +2056,7 @@ JSClassRef AccessibilityUIElement::getJSClass()
{ "elementTextLength", getElementTextLengthCallback, 0, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
{ "stringForSelection", stringForSelectionCallback, 0, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
{ "hasContainedByFieldsetTrait", hasContainedByFieldsetTraitCallback, 0, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
{ "hasTextEntryTrait", hasTextEntryTraitCallback, 0, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
{ "isSearchField", getIsSearchFieldCallback, 0, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
{ "isTextArea", getIsTextAreaCallback, 0, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
{ "isInsertion", getIsInsertionCallback, 0 , kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
Expand Down
1 change: 1 addition & 0 deletions Tools/DumpRenderTree/AccessibilityUIElement.h
Expand Up @@ -254,6 +254,7 @@ class AccessibilityUIElement {
bool scrollPageRight();

bool hasContainedByFieldsetTrait();
bool hasTextEntryTrait();
AccessibilityUIElement fieldsetAncestorElement();
JSRetainPtr<JSStringRef> attributedStringForElement();

Expand Down
7 changes: 7 additions & 0 deletions Tools/DumpRenderTree/ios/AccessibilityUIElementIOS.mm
Expand Up @@ -82,6 +82,7 @@ - (BOOL)accessibilityARIALiveRegionIsAtomic;
- (NSString *)accessibilityARIALiveRegionStatus;
- (NSString *)accessibilityARIARelevantStatus;
- (UIAccessibilityTraits)_axContainedByFieldsetTrait;
- (UIAccessibilityTraits)_axTextEntryTrait;
- (id)_accessibilityFieldsetAncestor;
- (BOOL)_accessibilityHasTouchEventListener;
- (NSString *)accessibilityExpandedTextValue;
Expand Down Expand Up @@ -189,6 +190,12 @@ - (CGPathRef)_accessibilityPath;
return (traits & [m_element _axContainedByFieldsetTrait]) == [m_element _axContainedByFieldsetTrait];
}

bool AccessibilityUIElement::hasTextEntryTrait()
{
UIAccessibilityTraits traits = [m_element accessibilityTraits];
return (traits & [m_element _axTextEntryTrait]) == [m_element _axTextEntryTrait];
}

AccessibilityUIElement AccessibilityUIElement::fieldsetAncestorElement()
{
id ancestorElement = [m_element _accessibilityFieldsetAncestor];
Expand Down
Expand Up @@ -84,6 +84,7 @@ bool AccessibilityUIElement::scrollPageDown() { return false; }
bool AccessibilityUIElement::scrollPageLeft() { return false; }
bool AccessibilityUIElement::scrollPageRight() { return false; }
bool AccessibilityUIElement::hasContainedByFieldsetTrait() { return false; }
bool AccessibilityUIElement::hasTextEntryTrait() { return false; }
RefPtr<AccessibilityUIElement> AccessibilityUIElement::fieldsetAncestorElement() { return nullptr; }
bool AccessibilityUIElement::isSearchField() const { return false; }
bool AccessibilityUIElement::isTextArea() const { return false; }
Expand Down
Expand Up @@ -417,6 +417,7 @@ class AccessibilityUIElement : public JSWrappable {
bool isInDescriptionListTerm() const;

bool hasContainedByFieldsetTrait();
bool hasTextEntryTrait();
RefPtr<AccessibilityUIElement> fieldsetAncestorElement();

bool isIsolatedObject() const;
Expand Down
Expand Up @@ -311,6 +311,7 @@
readonly attribute boolean isInDescriptionListTerm;

readonly attribute boolean hasContainedByFieldsetTrait;
readonly attribute boolean hasTextEntryTrait;
AccessibilityUIElement fieldsetAncestorElement();
AccessibilityUIElement focusableAncestor();
AccessibilityUIElement editableAncestor();
Expand Down
Expand Up @@ -92,6 +92,7 @@ - (NSString *)accessibilityARIALiveRegionStatus;
- (NSString *)accessibilityARIARelevantStatus;
- (NSString *)accessibilityInvalidStatus;
- (UIAccessibilityTraits)_axContainedByFieldsetTrait;
- (UIAccessibilityTraits)_axTextEntryTrait;
- (id)_accessibilityFieldsetAncestor;
- (BOOL)_accessibilityHasTouchEventListener;
- (NSString *)accessibilityExpandedTextValue;
Expand Down Expand Up @@ -870,6 +871,12 @@ - (CGPathRef)_accessibilityPath;
return (traits & [m_element _axContainedByFieldsetTrait]) == [m_element _axContainedByFieldsetTrait];
}

bool AccessibilityUIElement::hasTextEntryTrait()
{
UIAccessibilityTraits traits = [m_element accessibilityTraits];
return (traits & [m_element _axTextEntryTrait]) == [m_element _axTextEntryTrait];
}

RefPtr<AccessibilityUIElement> AccessibilityUIElement::fieldsetAncestorElement()
{
id ancestorElement = [m_element _accessibilityFieldsetAncestor];
Expand Down

0 comments on commit 31a6df5

Please sign in to comment.