forked from mirrors/gecko-dev
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:
parent
cb98b75272
commit
62ce834ce8
10 changed files with 182 additions and 0 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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()) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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() {}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
|
|
|
|||
|
|
@ -88,6 +88,13 @@
|
|||
|
||||
@end
|
||||
|
||||
@interface mozMeterAccessible : mozRangeAccessible
|
||||
|
||||
// override
|
||||
- (NSString*)moxValueDescription;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
* Base accessible for an incrementable, a settable range
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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."
|
||||
// );
|
||||
}
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in a new issue