Skip to content
Permalink
Browse files
AX: meter and progress elements are ignored when -webkit-appearance: …
…none is set

https://bugs.webkit.org/show_bug.cgi?id=232569
rdar://problem/84885223

Reviewed by Chris Fleizach.

`-webkit-appearance:none` and the unprefixed `appearance:none` on the
`meter` and `progress` elements cause them to generate plain `RenderBlock`s instead
of `RenderMeter` and `RenderProgress` renderers. This broke accessibility for these
elements because we specifically required one of these renderer types to create an
`AccessibilityProgressIndicator`.

With this patch, that requirement has been relaxed. Now an
`AccessibilityProgressIndicator` can be created with any `RenderObject`
that is associated with an `HTMLProgressElement` or `HTMLMeterElement`,
fixing this issue. `AccessibilityProgressIndicator` doesn't use any
method specific to either `RenderMeter` or `RenderProgress`, so this
strictness was unnecessary.

New test added:
accessibility/appearance-none-meter-and-progress-elements.html

* LayoutTests/accessibility/appearance-none-meter-and-progress-elements-expected.txt: Added.
* LayoutTests/accessibility/appearance-none-meter-and-progress-elements.html: Added.
* LayoutTests/platform/win/TestExpectations: Skip new test.
* Source/WebCore/accessibility/AXObjectCache.cpp:
(WebCore::createFromRenderer):
Refactor to remove unnecessary downcasting.
* Source/WebCore/accessibility/AccessibilityProgressIndicator.cpp:
(WebCore::AccessibilityProgressIndicator::AccessibilityProgressIndicator):
(WebCore::AccessibilityProgressIndicator::create):
(WebCore::AccessibilityProgressIndicator::valueDescription const):
(WebCore::AccessibilityProgressIndicator::valueForRange const):
(WebCore::AccessibilityProgressIndicator::maxValueForRange const):
(WebCore::AccessibilityProgressIndicator::minValueForRange const):
(WebCore::AccessibilityProgressIndicator::progressElement const):
(WebCore::AccessibilityProgressIndicator::meterElement const):
(WebCore::AccessibilityProgressIndicator::gaugeRegionValueDescription const):
(WebCore::AccessibilityProgressIndicator::element const): Deleted --
this override was not necessary.
* Source/WebCore/accessibility/AccessibilityProgressIndicator.h:

Canonical link: https://commits.webkit.org/255836@main
  • Loading branch information
twilco committed Oct 21, 2022
1 parent 96cc31b commit c67d110e0c94abf105c1c472fc3a5579eb68ff69
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 119 deletions.
@@ -0,0 +1,11 @@
This test ensures that <meter> and <progress> elements with appearance:none are accessible.

PASS: progress.role === 'AXRole: AXProgressIndicator'
PASS: progress.intValue === 4
PASS: meter.role === 'AXRole: AXLevelIndicator'
PASS: meter.valueDescription.includes('4 of 7') === true

PASS successfullyParsed is true

TEST COMPLETE

@@ -0,0 +1,39 @@
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<html>
<head>
<script src="../resources/accessibility-helper.js"></script>
<script src="../resources/js-test.js"></script>
<style>
meter, progress {
appearance: none;
}
</style>
</head>
<body>

<meter id="meter" min="0" max="7" value="4">
4 of 7
</meter>

<progress id="progress" min="0" max="7" value="4">
4 of 7
</progress>

<script>
var testOutput = "This test ensures that &lt;meter&gt; and &lt;progress&gt; elements with appearance:none are accessible.\n\n";

if (window.accessibilityController) {
var progress = accessibilityController.accessibleElementById("progress");
testOutput += expect("progress.role", "'AXRole: AXProgressIndicator'");
testOutput += expect("progress.intValue", "4");

var meter = accessibilityController.accessibleElementById("meter");
testOutput += expect("meter.role", "'AXRole: AXLevelIndicator'");
testOutput += expect("meter.valueDescription.includes('4 of 7')", "true");

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

@@ -1812,6 +1812,7 @@ webkit.org/b/140832 [ Release ] accessibility/aria-slider-value-change.html [ Fa
webkit.org/b/140832 [ Debug ] accessibility/aria-slider-value-change.html [ Skip ] # Debug assertion
webkit.org/b/140832 [ Release ] accessibility/aria-slider-value.html [ Failure ]
webkit.org/b/140832 [ Debug ] accessibility/aria-slider-value.html [ Skip ] # Debug assertion
webkit.org/b/140832 accessibility/appearance-none-meter-and-progress-elements.html [ Skip ]
webkit.org/b/140832 [ Release ] accessibility/meter-element.html [ Failure ]
webkit.org/b/140832 [ Debug ] accessibility/meter-element.html [ Skip ] # Debug assertion
webkit.org/b/140832 [ Release ] accessibility/progressbar.html [ Failure ]
@@ -86,6 +86,7 @@
#include "HTMLOptGroupElement.h"
#include "HTMLOptionElement.h"
#include "HTMLParserIdioms.h"
#include "HTMLProgressElement.h"
#include "HTMLSelectElement.h"
#include "HTMLTableElement.h"
#include "HTMLTablePartElement.h"
@@ -639,37 +640,34 @@ static Ref<AccessibilityObject> createFromRenderer(RenderObject* renderer)
return AccessibilityMathMLElement::create(renderer, isAnonymousOperator);
#endif

if (is<RenderBoxModelObject>(*renderer)) {
RenderBoxModelObject& cssBox = downcast<RenderBoxModelObject>(*renderer);
if (is<RenderListBox>(cssBox))
return AccessibilityListBox::create(&downcast<RenderListBox>(cssBox));
if (is<RenderMenuList>(cssBox))
return AccessibilityMenuList::create(&downcast<RenderMenuList>(cssBox));

// standard tables
if (is<RenderTable>(cssBox))
return AccessibilityTable::create(&downcast<RenderTable>(cssBox));
if (is<RenderTableRow>(cssBox))
return AccessibilityTableRow::create(&downcast<RenderTableRow>(cssBox));
if (is<RenderTableCell>(cssBox))
return AccessibilityTableCell::create(&downcast<RenderTableCell>(cssBox));

// progress bar
if (is<RenderProgress>(cssBox))
return AccessibilityProgressIndicator::create(&downcast<RenderProgress>(cssBox));
if (is<RenderListBox>(renderer))
return AccessibilityListBox::create(renderer);
if (is<RenderMenuList>(renderer))
return AccessibilityMenuList::create(downcast<RenderMenuList>(renderer));

// standard tables
if (is<RenderTable>(renderer))
return AccessibilityTable::create(renderer);
if (is<RenderTableRow>(renderer))
return AccessibilityTableRow::create(renderer);
if (is<RenderTableCell>(renderer))
return AccessibilityTableCell::create(renderer);

// progress bar
if (is<RenderProgress>(renderer) || is<HTMLProgressElement>(node))
return AccessibilityProgressIndicator::create(renderer);

#if ENABLE(ATTACHMENT_ELEMENT)
if (is<RenderAttachment>(cssBox))
return AccessibilityAttachment::create(&downcast<RenderAttachment>(cssBox));
if (is<RenderAttachment>(renderer))
return AccessibilityAttachment::create(downcast<RenderAttachment>(renderer));
#endif

if (is<RenderMeter>(cssBox))
return AccessibilityProgressIndicator::create(&downcast<RenderMeter>(cssBox));
if (is<RenderMeter>(renderer) || is<HTMLMeterElement>(node))
return AccessibilityProgressIndicator::create(renderer);

// input type=range
if (is<RenderSlider>(cssBox))
return AccessibilitySlider::create(&downcast<RenderSlider>(cssBox));
}
// input type=range
if (is<RenderSlider>(renderer))
return AccessibilitySlider::create(renderer);

return AccessibilityRenderObject::create(renderer);
}
@@ -35,22 +35,13 @@ namespace WebCore {

using namespace HTMLNames;

AccessibilityProgressIndicator::AccessibilityProgressIndicator(RenderProgress* renderer)
AccessibilityProgressIndicator::AccessibilityProgressIndicator(RenderObject* renderer)
: AccessibilityRenderObject(renderer)
{
ASSERT(is<RenderProgress>(renderer) || is<RenderMeter>(renderer) || is<HTMLProgressElement>(renderer->node()) || is<HTMLMeterElement>(renderer->node()));
}

Ref<AccessibilityProgressIndicator> AccessibilityProgressIndicator::create(RenderProgress* renderer)
{
return adoptRef(*new AccessibilityProgressIndicator(renderer));
}

AccessibilityProgressIndicator::AccessibilityProgressIndicator(RenderMeter* renderer)
: AccessibilityRenderObject(renderer)
{
}

Ref<AccessibilityProgressIndicator> AccessibilityProgressIndicator::create(RenderMeter* renderer)
Ref<AccessibilityProgressIndicator> AccessibilityProgressIndicator::create(RenderObject* renderer)
{
return adoptRef(*new AccessibilityProgressIndicator(renderer));
}
@@ -67,18 +58,14 @@ String AccessibilityProgressIndicator::valueDescription() const
if (!description.isEmpty())
return description;

if (!m_renderer->isMeter())
return description;

HTMLMeterElement* meter = meterElement();
auto* meter = meterElement();
if (!meter)
return description;

// The HTML spec encourages authors to include a textual representation of the meter's state in
// the element's contents. We'll fall back on that if there is not a more accessible alternative.
AccessibilityObject* axMeter = axObjectCache()->getOrCreate(meter);
if (is<AccessibilityNodeObject>(axMeter))
description = downcast<AccessibilityNodeObject>(axMeter)->accessibilityDescriptionForChildren();
if (auto* nodeObject = dynamicDowncast<AccessibilityNodeObject>(axObjectCache()->getOrCreate(meter)))
description = nodeObject->accessibilityDescriptionForChildren();

if (description.isEmpty())
description = meter->textContent();
@@ -92,55 +79,35 @@ String AccessibilityProgressIndicator::valueDescription() const

float AccessibilityProgressIndicator::valueForRange() const
{
if (!m_renderer)
return 0.0;

if (m_renderer->isProgress()) {
HTMLProgressElement* progress = progressElement();
if (progress && progress->position() >= 0)
return narrowPrecisionToFloat(progress->value());
}
if (auto* progress = progressElement(); progress && progress->position() >= 0)
return narrowPrecisionToFloat(progress->value());

if (m_renderer->isMeter()) {
if (HTMLMeterElement* meter = meterElement())
return narrowPrecisionToFloat(meter->value());
}
if (auto* meter = meterElement())
return narrowPrecisionToFloat(meter->value());

// Indeterminate progress bar should return 0.
return 0.0;
}

float AccessibilityProgressIndicator::maxValueForRange() const
{
if (!m_renderer)
return 0.0;
if (auto* progress = progressElement())
return narrowPrecisionToFloat(progress->max());

if (m_renderer->isProgress()) {
if (HTMLProgressElement* progress = progressElement())
return narrowPrecisionToFloat(progress->max());
}

if (m_renderer->isMeter()) {
if (HTMLMeterElement* meter = meterElement())
return narrowPrecisionToFloat(meter->max());
}
if (auto* meter = meterElement())
return narrowPrecisionToFloat(meter->max());

return 0.0;
}

float AccessibilityProgressIndicator::minValueForRange() const
{
if (!m_renderer)
if (auto* progress = progressElement())
return 0.0;

if (m_renderer->isProgress())
return 0.0;

if (m_renderer->isMeter()) {
if (HTMLMeterElement* meter = meterElement())
return narrowPrecisionToFloat(meter->min());
}


if (auto* meter = meterElement())
return narrowPrecisionToFloat(meter->min());

return 0.0;
}

@@ -153,59 +120,36 @@ AccessibilityRole AccessibilityProgressIndicator::roleValue() const

HTMLProgressElement* AccessibilityProgressIndicator::progressElement() const
{
if (!is<RenderProgress>(*m_renderer))
return nullptr;

return downcast<RenderProgress>(*m_renderer).progressElement();
return dynamicDowncast<HTMLProgressElement>(node());
}

HTMLMeterElement* AccessibilityProgressIndicator::meterElement() const
{
if (!is<RenderMeter>(*m_renderer))
return nullptr;

return downcast<RenderMeter>(*m_renderer).meterElement();
return dynamicDowncast<HTMLMeterElement>(node());
}

String AccessibilityProgressIndicator::gaugeRegionValueDescription() const
{
#if PLATFORM(COCOA)
if (!m_renderer || !m_renderer->isMeter())
auto* meterElement = this->meterElement();
if (!meterElement)
return String();

// Only expose this when the author has explicitly specified the following attributes.
if (!hasAttribute(lowAttr) && !hasAttribute(highAttr) && !hasAttribute(optimumAttr))
return String();

if (HTMLMeterElement* element = meterElement()) {
switch (element->gaugeRegion()) {
case HTMLMeterElement::GaugeRegionOptimum:
return AXMeterGaugeRegionOptimumText();
case HTMLMeterElement::GaugeRegionSuboptimal:
return AXMeterGaugeRegionSuboptimalText();
case HTMLMeterElement::GaugeRegionEvenLessGood:
return AXMeterGaugeRegionLessGoodText();
default:
break;
}
switch (meterElement->gaugeRegion()) {
case HTMLMeterElement::GaugeRegionOptimum:
return AXMeterGaugeRegionOptimumText();
case HTMLMeterElement::GaugeRegionSuboptimal:
return AXMeterGaugeRegionSuboptimalText();
case HTMLMeterElement::GaugeRegionEvenLessGood:
return AXMeterGaugeRegionLessGoodText();
}
#endif
return String();
}

Element* AccessibilityProgressIndicator::element() const
{
if (!m_renderer)
return nullptr;

if (m_renderer->isProgress())
return progressElement();

if (m_renderer->isMeter())
return meterElement();

return AccessibilityObject::element();
}

} // namespace WebCore

@@ -32,9 +32,7 @@ class RenderProgress;

class AccessibilityProgressIndicator final : public AccessibilityRenderObject {
public:
static Ref<AccessibilityProgressIndicator> create(RenderProgress*);
static Ref<AccessibilityProgressIndicator> create(RenderMeter*);
Element* element() const override;
static Ref<AccessibilityProgressIndicator> create(RenderObject*);

private:
AccessibilityRole roleValue() const override;
@@ -46,10 +44,8 @@ class AccessibilityProgressIndicator final : public AccessibilityRenderObject {
float maxValueForRange() const override;
float minValueForRange() const override;

explicit AccessibilityProgressIndicator(RenderProgress*);
explicit AccessibilityProgressIndicator(RenderObject*);
HTMLProgressElement* progressElement() const;

explicit AccessibilityProgressIndicator(RenderMeter*);
HTMLMeterElement* meterElement() const;

bool computeAccessibilityIsIgnored() const override;

0 comments on commit c67d110

Please sign in to comment.