Title: [278683] trunk
Revision
278683
Author
[email protected]
Date
2021-06-09 17:11:41 -0700 (Wed, 09 Jun 2021)

Log Message

iOS - VoiceOver reads the old heading text when updated with heading.firstChild.data.
https://bugs.webkit.org/show_bug.cgi?id=226754
Source/WebCore:

rdar://44949563

Reviewed by Chris Fleizach.

Tests: accessibility/ios-simulator/heading-text-updates.html
       accessibility/mac/heading-text-updates.html

The problem was caused by [WebAccessibilityObjectWrapper _accessibilityTraitsFromAncestors]
setting the value and label of static text inside headings. since this
method is called only on the initialization of the object, the label is
never updated when the text changes.
The solution is to move the logic to return the label and value of
static text inside headings to the accessibilityLabel and accessibilityValue
respectively.

* accessibility/ios/WebAccessibilityObjectWrapperIOS.mm:
(-[WebAccessibilityObjectWrapper _accessibilityTraitsFromAncestors]):
(-[WebAccessibilityObjectWrapper accessibilityLabel]):
(-[WebAccessibilityObjectWrapper accessibilityValue]):

LayoutTests:

Reviewed by Chris Fleizach.

* accessibility/ios-simulator/heading-text-updates-expected.txt: Added.
* accessibility/ios-simulator/heading-text-updates.html: Added.
* accessibility/mac/heading-text-updates-expected.txt: Added.
* accessibility/mac/heading-text-updates.html: Added.

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (278682 => 278683)


--- trunk/LayoutTests/ChangeLog	2021-06-09 23:44:40 UTC (rev 278682)
+++ trunk/LayoutTests/ChangeLog	2021-06-10 00:11:41 UTC (rev 278683)
@@ -1,3 +1,15 @@
+2021-06-09  Andres Gonzalez  <[email protected]>
+
+        iOS - VoiceOver reads the old heading text when updated with heading.firstChild.data.
+        https://bugs.webkit.org/show_bug.cgi?id=226754
+
+        Reviewed by Chris Fleizach.
+
+        * accessibility/ios-simulator/heading-text-updates-expected.txt: Added.
+        * accessibility/ios-simulator/heading-text-updates.html: Added.
+        * accessibility/mac/heading-text-updates-expected.txt: Added.
+        * accessibility/mac/heading-text-updates.html: Added.
+
 2021-06-09  Devin Rousso  <[email protected]>
 
         (r278618) media/modern-media-controls/overflow-support/chapters.html is timing out since introduction

Added: trunk/LayoutTests/accessibility/ios-simulator/heading-text-updates-expected.txt (0 => 278683)


--- trunk/LayoutTests/accessibility/ios-simulator/heading-text-updates-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/accessibility/ios-simulator/heading-text-updates-expected.txt	2021-06-10 00:11:41 UTC (rev 278683)
@@ -0,0 +1,75 @@
+This tests that changing the text in headings by four different DOM methods, results on the corresponding text being retrieved via the accessibility property.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+h1
+AXValue: 1
+AXLabel: original text
+h2
+AXValue: 2
+AXLabel: original text
+h3
+AXValue: 3
+AXLabel: original text
+divheading
+AXValue: 2
+AXLabel: original text
+h1
+AXValue: 1
+AXLabel: innerHTML
+h2
+AXValue: 2
+AXLabel: innerHTML
+h3
+AXValue: 3
+AXLabel: innerHTML
+divheading
+AXValue: 2
+AXLabel: innerHTML
+h1
+AXValue: 1
+AXLabel: innerText
+h2
+AXValue: 2
+AXLabel: innerText
+h3
+AXValue: 3
+AXLabel: innerText
+divheading
+AXValue: 2
+AXLabel: innerText
+h1
+AXValue: 1
+AXLabel: textContent
+h2
+AXValue: 2
+AXLabel: textContent
+h3
+AXValue: 3
+AXLabel: textContent
+divheading
+AXValue: 2
+AXLabel: textContent
+h1
+AXValue: 1
+AXLabel: firstChild.data
+h2
+AXValue: 2
+AXLabel: firstChild.data
+h3
+AXValue: 3
+AXLabel: firstChild.data
+divheading
+AXValue: 2
+AXLabel: firstChild.data
+PASS successfullyParsed is true
+
+TEST COMPLETE
+firstChild.data
+
+firstChild.data
+
+firstChild.data
+
+firstChild.data

Added: trunk/LayoutTests/accessibility/ios-simulator/heading-text-updates.html (0 => 278683)


--- trunk/LayoutTests/accessibility/ios-simulator/heading-text-updates.html	                        (rev 0)
+++ trunk/LayoutTests/accessibility/ios-simulator/heading-text-updates.html	2021-06-10 00:11:41 UTC (rev 278683)
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src=""
+</head>
+<body>
+
+<h1 class="output" id="h1">original text</h1>
+<h2 class="output" id="h2">original text</h2>
+<h3 class="output" id="h3">original text</h3>
+<div role="heading" class="output" id="divheading">original text</div>
+
+<script>
+    description("This tests that changing the text in headings by four different DOM methods, results on the corresponding text being retrieved via the accessibility property.");
+
+    var ids = [ "h1", "h2", "h3", "divheading" ];
+
+    function setValue(value) {
+        [...document.querySelectorAll('.output')].forEach((node) => {
+            node[value] = `${value}`;
+        });
+    }
+
+    function setFirstChildData(value) {
+        [...document.querySelectorAll('.output')].forEach((node) => {
+            node.firstChild.data = ""
+        });
+    }
+
+    function checkAXLabels() {
+        ids.forEach((id) => {
+            debug(id);
+            axElement = accessibilityController.accessibleElementById(id);
+            debug(axElement.childAtIndex(0).stringValue);
+            debug(axElement.childAtIndex(0).description);
+        });
+    }
+
+    if (window.accessibilityController) {
+        // Log original text.
+        checkAXLabels();
+
+        setValue("innerHTML");
+        checkAXLabels();
+
+        setValue("innerText");
+        checkAXLabels();
+
+        setValue("textContent");
+        checkAXLabels();
+
+        setFirstChildData("firstChild.data");
+        checkAXLabels();
+    }
+</script>
+</body>
+</html>

Added: trunk/LayoutTests/accessibility/mac/heading-text-updates-expected.txt (0 => 278683)


--- trunk/LayoutTests/accessibility/mac/heading-text-updates-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/accessibility/mac/heading-text-updates-expected.txt	2021-06-10 00:11:41 UTC (rev 278683)
@@ -0,0 +1,75 @@
+This tests that changing the text in headings by four different DOM methods, results on the corresponding text being retrieved via the accessibility property.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+h1
+AXValue: 1
+AXValue: original text
+h2
+AXValue: 2
+AXValue: original text
+h3
+AXValue: 3
+AXValue: original text
+divheading
+AXValue: 2
+AXValue: original text
+h1
+AXValue: 1
+AXValue: innerHTML
+h2
+AXValue: 2
+AXValue: innerHTML
+h3
+AXValue: 3
+AXValue: innerHTML
+divheading
+AXValue: 2
+AXValue: innerHTML
+h1
+AXValue: 1
+AXValue: innerText
+h2
+AXValue: 2
+AXValue: innerText
+h3
+AXValue: 3
+AXValue: innerText
+divheading
+AXValue: 2
+AXValue: innerText
+h1
+AXValue: 1
+AXValue: textContent
+h2
+AXValue: 2
+AXValue: textContent
+h3
+AXValue: 3
+AXValue: textContent
+divheading
+AXValue: 2
+AXValue: textContent
+h1
+AXValue: 1
+AXValue: firstChild.data
+h2
+AXValue: 2
+AXValue: firstChild.data
+h3
+AXValue: 3
+AXValue: firstChild.data
+divheading
+AXValue: 2
+AXValue: firstChild.data
+PASS successfullyParsed is true
+
+TEST COMPLETE
+firstChild.data
+
+firstChild.data
+
+firstChild.data
+
+firstChild.data

Added: trunk/LayoutTests/accessibility/mac/heading-text-updates.html (0 => 278683)


--- trunk/LayoutTests/accessibility/mac/heading-text-updates.html	                        (rev 0)
+++ trunk/LayoutTests/accessibility/mac/heading-text-updates.html	2021-06-10 00:11:41 UTC (rev 278683)
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src=""
+</head>
+<body>
+
+<h1 class="output" id="h1">original text</h1>
+<h2 class="output" id="h2">original text</h2>
+<h3 class="output" id="h3">original text</h3>
+<div role="heading" class="output" id="divheading">original text</div>
+
+<script>
+    description("This tests that changing the text in headings by four different DOM methods, results on the corresponding text being retrieved via the accessibility property.");
+
+    var ids = [ "h1", "h2", "h3", "divheading" ];
+
+    function setValue(value) {
+        [...document.querySelectorAll('.output')].forEach((node) => {
+            node[value] = `${value}`;
+        });
+    }
+
+    function setFirstChildData(value) {
+        [...document.querySelectorAll('.output')].forEach((node) => {
+            node.firstChild.data = ""
+        });
+    }
+
+    function checkAXLabels() {
+        ids.forEach((id) => {
+            debug(id);
+            axElement = accessibilityController.accessibleElementById(id);
+            debug(axElement.stringValue);
+            debug(axElement.childAtIndex(0).stringValue);
+        });
+    }
+
+    if (window.accessibilityController) {
+        // Log original text.
+        checkAXLabels();
+
+        setValue("innerHTML");
+        checkAXLabels();
+
+        setValue("innerText");
+        checkAXLabels();
+
+        setValue("textContent");
+        checkAXLabels();
+
+        setFirstChildData("firstChild.data");
+        checkAXLabels();
+    }
+</script>
+</body>
+</html>

Modified: trunk/Source/WebCore/ChangeLog (278682 => 278683)


--- trunk/Source/WebCore/ChangeLog	2021-06-09 23:44:40 UTC (rev 278682)
+++ trunk/Source/WebCore/ChangeLog	2021-06-10 00:11:41 UTC (rev 278683)
@@ -1,3 +1,27 @@
+2021-06-09  Andres Gonzalez  <[email protected]>
+
+        iOS - VoiceOver reads the old heading text when updated with heading.firstChild.data.
+        https://bugs.webkit.org/show_bug.cgi?id=226754
+        rdar://44949563
+
+        Reviewed by Chris Fleizach.
+
+        Tests: accessibility/ios-simulator/heading-text-updates.html
+               accessibility/mac/heading-text-updates.html
+
+        The problem was caused by [WebAccessibilityObjectWrapper _accessibilityTraitsFromAncestors]
+        setting the value and label of static text inside headings. since this
+        method is called only on the initialization of the object, the label is
+        never updated when the text changes.
+        The solution is to move the logic to return the label and value of
+        static text inside headings to the accessibilityLabel and accessibilityValue
+        respectively.
+
+        * accessibility/ios/WebAccessibilityObjectWrapperIOS.mm:
+        (-[WebAccessibilityObjectWrapper _accessibilityTraitsFromAncestors]):
+        (-[WebAccessibilityObjectWrapper accessibilityLabel]):
+        (-[WebAccessibilityObjectWrapper accessibilityValue]):
+
 2021-06-09  Eric Carlson  <[email protected]>
 
         Nullptr crash in MediaSource::updateBufferedIfNeeded

Modified: trunk/Source/WebCore/accessibility/ios/WebAccessibilityObjectWrapperIOS.mm (278682 => 278683)


--- trunk/Source/WebCore/accessibility/ios/WebAccessibilityObjectWrapperIOS.mm	2021-06-09 23:44:40 UTC (rev 278682)
+++ trunk/Source/WebCore/accessibility/ios/WebAccessibilityObjectWrapperIOS.mm	2021-06-10 00:11:41 UTC (rev 278683)
@@ -661,15 +661,15 @@
 - (uint64_t)_accessibilityTraitsFromAncestors
 {
     uint64_t traits = 0;
-    AccessibilityRole role = self.axBackingObject->roleValue();
-    
+    auto* backingObject = self.axBackingObject;
+
     // Trait information also needs to be gathered from the parents above the object.
-    // The parentObject is needed instead of the unignoredParentObject, because a table might be ignored, but information still needs to be gathered from it.    
-    for (auto* parent = self.axBackingObject->parentObject(); parent != nil; parent = parent->parentObject()) {
+    // The parentObject is needed instead of the unignoredParentObject, because a table might be ignored, but information still needs to be gathered from it.
+    for (auto* parent = backingObject->parentObject(); parent; parent = parent->parentObject()) {
         AccessibilityRole parentRole = parent->roleValue();
         if (parentRole == AccessibilityRole::WebArea)
             break;
-        
+
         switch (parentRole) {
         case AccessibilityRole::Link:
         case AccessibilityRole::WebCoreLink:
@@ -677,35 +677,20 @@
             if (parent->isVisited())
                 traits |= [self _axVisitedTrait];
             break;
-        case AccessibilityRole::Heading: {
+        case AccessibilityRole::Heading:
             traits |= [self _axHeaderTrait];
-            // If this object has the header trait, we should set the value
-            // to the heading level. If it was a static text element, we need to store
-            // the value as the label, because the heading level needs to the value.
-            AccessibilityObjectWrapper* wrapper = parent->wrapper();
-            if (role == AccessibilityRole::StaticText) {
-                // We should only set the text value as the label when there's no
-                // alternate text on the heading parent.
-                NSString *headingLabel = [wrapper baseAccessibilityDescription];
-                if (![headingLabel length])
-                    [self setAccessibilityLabel:self.axBackingObject->stringValue()];
-                else
-                    [self setAccessibilityLabel:headingLabel];
-            }
-            [self setAccessibilityValue:[wrapper accessibilityValue]];
             break;
-        }
         default:
             if ([self _accessibilityIsLandmarkRole:parentRole])
                 traits |= [self _axContainedByLandmarkTrait];
             break;
         }
-        
+
         // If this object has fieldset parent, we should add containedByFieldsetTrait to it.
         if (parent->isFieldset())
             traits |= [self _axContainedByFieldsetTrait];
     }
-    
+
     return traits;
 }
 
@@ -1189,35 +1174,56 @@
     if (label)
         return label;
 
+    auto* backingObject = self.axBackingObject;
+
     // iOS doesn't distinguish between a title and description field,
     // so concatentation will yield the best result.
-    NSString *axTitle = self.axBackingObject->titleAttributeValue();
-    NSString *axDescription = [self baseAccessibilityDescription];
+    NSString *axTitle = backingObject->titleAttributeValue();
+    NSString *axDescription = backingObject->descriptionAttributeValue();
     NSString *landmarkDescription = [self ariaLandmarkRoleDescription];
     NSString *interactiveVideoDescription = [self interactiveVideoDescription];
-    
+
+    // If self is static text inside a heading, the label should be the string
+    // value of the static text object, except when the heading has alternative
+    // text, in which case, that alternative text is returned here.
+    // The reason is that the string value for static text inside a heading is
+    // used to convey the heading level instead.
+    if (backingObject->roleValue() == AccessibilityRole::StaticText
+        && self.accessibilityTraits & self._axHeaderTrait) {
+        auto* heading = Accessibility::findAncestor(*backingObject, false, [] (const auto& ancestor) {
+            return ancestor.roleValue() == AccessibilityRole::Heading;
+        });
+
+        if (heading) {
+            auto headingLabel = heading->descriptionAttributeValue();
+            if (!headingLabel.isEmpty())
+                return headingLabel;
+            return backingObject->stringValue();
+        }
+    }
+
     // We should expose the value of the input type date or time through AXValue instead of AXTitle.
-    if (self.axBackingObject->isInputTypePopupButton() && [axTitle isEqualToString:[self accessibilityValue]])
+    if (backingObject->isInputTypePopupButton() && [axTitle isEqualToString:[self accessibilityValue]])
         axTitle = nil;
 
     // Footer is not considered a landmark, but we want the role description.
-    if (self.axBackingObject->roleValue() == AccessibilityRole::Footer)
+    if (backingObject->roleValue() == AccessibilityRole::Footer)
         landmarkDescription = AXFooterRoleDescriptionText();
 
     NSMutableString *result = [NSMutableString string];
-    if (self.axBackingObject->roleValue() == AccessibilityRole::HorizontalRule)
+    if (backingObject->roleValue() == AccessibilityRole::HorizontalRule)
         appendStringToResult(result, AXHorizontalRuleDescriptionText());
 
     appendStringToResult(result, axTitle);
     appendStringToResult(result, axDescription);
     if ([self stringValueShouldBeUsedInLabel]) {
-        NSString *valueLabel = self.axBackingObject->stringValue();
+        NSString *valueLabel = backingObject->stringValue();
         valueLabel = [valueLabel stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
         appendStringToResult(result, valueLabel);
     }
     appendStringToResult(result, landmarkDescription);
     appendStringToResult(result, interactiveVideoDescription);
-    
+
     return [result length] ? result : nil;
 }
 
@@ -1464,14 +1470,15 @@
 {
     if (![self _prepareAccessibilityCall])
         return nil;
-    
+
     // check if the value was overridden
     NSString *value = [super accessibilityValue];
     if (value)
         return value;
-    
-    if (self.axBackingObject->supportsCheckedState()) {
-        switch (self.axBackingObject->checkboxOrRadioValue()) {
+
+    auto* backingObject = self.axBackingObject;
+    if (backingObject->supportsCheckedState()) {
+        switch (backingObject->checkboxOrRadioValue()) {
         case AccessibilityButtonState::Off:
             return [NSString stringWithFormat:@"%d", 0];
         case AccessibilityButtonState::On:
@@ -1482,38 +1489,46 @@
         ASSERT_NOT_REACHED();
         return [NSString stringWithFormat:@"%d", 0];
     }
-    
-    if (self.axBackingObject->isButton() && self.axBackingObject->isPressed())
+
+    if (backingObject->isButton() && backingObject->isPressed())
         return [NSString stringWithFormat:@"%d", 1];
 
+    // If self has the header trait, value should be the heading level.
+    if (self.accessibilityTraits & self._axHeaderTrait) {
+        auto* heading = Accessibility::findAncestor(*backingObject, true, [] (const auto& ancestor) {
+            return ancestor.roleValue() == AccessibilityRole::Heading;
+        });
+        ASSERT(heading);
+
+        if (heading)
+            return [NSString stringWithFormat:@"%d", heading->headingLevel()];
+    }
+
     // rdar://8131388 WebKit should expose the same info as UIKit for its password fields.
-    if (self.axBackingObject->isPasswordField() && ![self _accessibilityIsStrongPasswordField]) {
-        int passwordLength = self.axBackingObject->accessibilityPasswordFieldLength();
+    if (backingObject->isPasswordField() && ![self _accessibilityIsStrongPasswordField]) {
+        int passwordLength = backingObject->accessibilityPasswordFieldLength();
         NSMutableString* string = [NSMutableString string];
         for (int k = 0; k < passwordLength; ++k)
             [string appendString:@"•"];
         return string;
     }
-    
+
     // A text control should return its text data as the axValue (per iPhone AX API).
     if (![self stringValueShouldBeUsedInLabel])
-        return self.axBackingObject->stringValue();
-    
-    if (self.axBackingObject->isRangeControl()) {
+        return backingObject->stringValue();
+
+    if (backingObject->isRangeControl()) {
         // Prefer a valueDescription if provided by the author (through aria-valuetext).
-        String valueDescription = self.axBackingObject->valueDescription();
+        String valueDescription = backingObject->valueDescription();
         if (!valueDescription.isEmpty())
             return valueDescription;
 
-        return [NSString stringWithFormat:@"%.2f", self.axBackingObject->valueForRange()];
+        return [NSString stringWithFormat:@"%.2f", backingObject->valueForRange()];
     }
 
-    if (is<AccessibilityAttachment>(self.axBackingObject) && downcast<AccessibilityAttachment>(self.axBackingObject)->hasProgress())
-        return [NSString stringWithFormat:@"%.2f", self.axBackingObject->valueForRange()];
-    
-    if (self.axBackingObject->isHeading())
-        return [NSString stringWithFormat:@"%d", self.axBackingObject->headingLevel()];
-    
+    if (is<AccessibilityAttachment>(backingObject) && downcast<AccessibilityAttachment>(backingObject)->hasProgress())
+        return [NSString stringWithFormat:@"%.2f", backingObject->valueForRange()];
+
     return nil;
 }
 
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to