Bug 1895408: Add support for exposing critical, supoptimal, optimal meter values via AXValueDescription r=Jamie

Differential Revision: https://phabricator.services.mozilla.com/D209615
This commit is contained in:
Morgan Rae Reschenberg 2024-05-21 22:11:05 +00:00
parent cb98b75272
commit 62ce834ce8
10 changed files with 182 additions and 0 deletions

View file

@ -243,6 +243,8 @@ class CacheKey {
static constexpr nsStaticAtom* TextValue = nsGkAtoms::aria_valuetext;
// gfx::Matrix4x4, CacheDomain::TransformMatrix
static constexpr nsStaticAtom* TransformMatrix = nsGkAtoms::transform;
// int32_t, CacheDomain::Value
static constexpr nsStaticAtom* ValueRegion = nsGkAtoms::valuetype;
// nsTArray<uint64_t>, CacheDomain::Viewport
// The list of Accessibles in the viewport used for hit testing and on-screen
// determination.

View file

@ -3452,6 +3452,14 @@ already_AddRefed<AccAttributes> LocalAccessible::BundleFieldsForCache(
fields->SetAttribute(CacheKey::SrcURL, DeleteEntry());
}
}
if (TagName() == nsGkAtoms::meter) {
// We should only cache value region for HTML meter elements. A meter
// should always have a value region, so this attribute should never
// be empty (i.e. there is no DeleteEntry() clause here).
HTMLMeterAccessible* meter = static_cast<HTMLMeterAccessible*>(this);
fields->SetAttribute(CacheKey::ValueRegion, meter->ValueRegion());
}
}
if (aCacheDomain & CacheDomain::Viewport && IsDoc()) {

View file

@ -18,6 +18,7 @@
#include "nsContentList.h"
#include "mozilla/dom/HTMLInputElement.h"
#include "mozilla/dom/HTMLMeterElement.h"
#include "mozilla/dom/HTMLTextAreaElement.h"
#include "mozilla/dom/HTMLFormControlsCollection.h"
#include "nsIFormControl.h"
@ -965,6 +966,39 @@ bool HTMLMeterAccessible::SetCurValue(double aValue) {
return false; // meters are readonly.
}
int32_t HTMLMeterAccessible::ValueRegion() const {
dom::HTMLMeterElement* elm = dom::HTMLMeterElement::FromNode(mContent);
if (!elm) {
return -1;
}
double high = elm->High();
double low = elm->Low();
double optimum = elm->Optimum();
double value = elm->Value();
// For more information on how these regions are defined, see
// "UA requirements for regions of the gauge"
// https://html.spec.whatwg.org/multipage/form-elements.html#the-meter-element
if (optimum > high) {
if (value > high) {
return 1;
}
return value > low ? 0 : -1;
}
if (optimum < low) {
if (value < low) {
return 1;
}
return value < high ? 0 : -1;
}
// optimum is between low and high, inclusive
if (value >= low && value <= high) {
return 1;
}
// Both upper and lower regions are considered equally
// non-optimal.
return 0;
}
void HTMLMeterAccessible::DOMAttributeChanged(int32_t aNameSpaceID,
nsAtom* aAttribute,
int32_t aModType,
@ -976,4 +1010,12 @@ void HTMLMeterAccessible::DOMAttributeChanged(int32_t aNameSpaceID,
if (aAttribute == nsGkAtoms::value) {
mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, this);
}
if (aAttribute == nsGkAtoms::high || aAttribute == nsGkAtoms::low ||
aAttribute == nsGkAtoms::optimum) {
// Our meter's value region may have changed, queue an update for
// the value domain.
mDoc->QueueCacheUpdate(this, CacheDomain::Value);
return;
}
}

View file

@ -335,6 +335,17 @@ class HTMLMeterAccessible : public LeafAccessible {
// Widgets
virtual bool IsWidget() const override;
// HTMLMeterAccessible
/**
* Given the low, high, and optimum attrs from DOM, return an int
* that indicates which region the current value falls in:
* - Optimal (1)
* - Suboptimal (0)
* - Critical, or "even less good" by the spec (-1)
*/
int32_t ValueRegion() const;
protected:
virtual ~HTMLMeterAccessible() {}

View file

@ -1348,6 +1348,19 @@ void RemoteAccessible::Announce(const nsString& aAnnouncement,
}
#endif // !defined(XP_WIN)
int32_t RemoteAccessible::ValueRegion() const {
MOZ_ASSERT(TagName() == nsGkAtoms::meter,
"Accessing value region on non-meter element?");
if (mCachedFields) {
if (auto region =
mCachedFields->GetAttribute<int32_t>(CacheKey::ValueRegion)) {
return *region;
}
}
// Expose sub-optimal (but not critical) as the value region, as a fallback.
return 0;
}
void RemoteAccessible::ScrollSubstringToPoint(int32_t aStartOffset,
int32_t aEndOffset,
uint32_t aCoordinateType,

View file

@ -374,6 +374,9 @@ class RemoteAccessible : public Accessible, public HyperTextAccessibleBase {
void Announce(const nsString& aAnnouncement, uint16_t aPriority);
#endif // !defined(XP_WIN)
// HTMLMeterAccessible
int32_t ValueRegion() const;
// HyperTextAccessibleBase
virtual already_AddRefed<AccAttributes> DefaultTextAttributes() override;

View file

@ -195,6 +195,9 @@ Class a11y::GetTypeFromRole(roles::Role aRole) {
case roles::PROGRESSBAR:
return [mozRangeAccessible class];
case roles::METER:
return [mozMeterAccessible class];
case roles::SPINBUTTON:
case roles::SLIDER:
return [mozIncrementableAccessible class];

View file

@ -88,6 +88,13 @@
@end
@interface mozMeterAccessible : mozRangeAccessible
// override
- (NSString*)moxValueDescription;
@end
/**
* Base accessible for an incrementable, a settable range
*/

View file

@ -177,6 +177,48 @@ using namespace mozilla::a11y;
@end
@implementation mozMeterAccessible
- (NSString*)moxValueDescription {
nsAutoString valueDesc;
mGeckoAccessible->Value(valueDesc);
if (mGeckoAccessible->TagName() != nsGkAtoms::meter) {
// We're dealing with an aria meter, which shouldn't get
// a value region.
return nsCocoaUtils::ToNSString(valueDesc);
}
if (!valueDesc.IsEmpty()) {
// Append a comma to separate the existing value description
// from the value region.
valueDesc.Append(u", "_ns);
}
// We need to concat the given value description
// with a description of the value as either optimal,
// suboptimal, or critical.
int32_t region;
if (mGeckoAccessible->IsRemote()) {
region = mGeckoAccessible->AsRemote()->ValueRegion();
} else {
HTMLMeterAccessible* localMeter =
static_cast<HTMLMeterAccessible*>(mGeckoAccessible->AsLocal());
region = localMeter->ValueRegion();
}
if (region == 1) {
valueDesc.Append(u"Optimal value"_ns);
} else if (region == 0) {
valueDesc.Append(u"Suboptimal value"_ns);
} else {
MOZ_ASSERT(region == -1);
valueDesc.Append(u"Critical value"_ns);
}
return nsCocoaUtils::ToNSString(valueDesc);
}
@end
@implementation mozIncrementableAccessible
- (NSString*)moxValueDescription {

View file

@ -242,3 +242,54 @@ addAccessibleTask(
is(progress.getAttributeValue("AXValue"), 90, "Correct updated value");
}
);
/**
* Verify meter HTML elements expose the value region as part of their value
* description.
*/
addAccessibleTask(
`<label for="fuel">Fuel level:</label><meter id="fuel" min="0" max="100" low="33" high="66" optimum="80" value="50"></meter>`,
async (browser, accDoc) => {
const meter = getNativeInterface(accDoc, "fuel");
is(meter.getAttributeValue("AXValue"), 50, "Correct value");
is(
meter.getAttributeValue("AXValueDescription"),
"50, Suboptimal value",
"Value description contains appropriate value region"
);
let evt = waitForMacEvent("AXValueChanged");
await invokeContentTask(browser, [], () => {
const f = content.document.getElementById("fuel");
f.setAttribute("value", "90");
});
await evt;
is(
meter.getAttributeValue("AXValueDescription"),
"90, Optimal value",
"Value description updated to optimal"
);
await invokeContentTask(browser, [], () => {
const f = content.document.getElementById("fuel");
f.setAttribute("optimum", "20");
});
await untilCacheIs(
() => meter.getAttributeValue("AXValueDescription"),
"90, Critical value",
"Value description updated to critical."
);
// XXX bug 1895627:
// await invokeContentTask(browser, [], () => {
// const f = content.document.getElementById("fuel");
// f.textContent = "at 90/100";
// });
// await untilCacheIs(
// () => meter.getAttributeValue("AXValueDescription"),
// "at 90/100, Critical value",
// "Value description updated to include inner text."
// );
}
);