Bug 1901461 - Part 2: Implement UIA_AnnotationTypesAttributeId, r=Jamie

This revision implements the annotation types attribute and a few of its
associated annotation types. Namely, implemented here are SpellingError,
GrammarError, DataValidationError, and Highlighted. This revision also
implements tests for this new functionality.

Differential Revision: https://phabricator.services.mozilla.com/D227893
This commit is contained in:
Nathan LaPre
2024-11-06 19:26:59 +00:00
parent 5ee0a6fa75
commit 3de3a25a15
2 changed files with 215 additions and 1 deletions

View File

@@ -535,6 +535,10 @@ addUiaTask(
<div id="superscript-container">a <sup>bcd</sup> ef</div>
<div id="not-hidden-container">a bcd ef</div>
<div id="readonly-container">a <span contenteditable="true">bcd</span> ef</div>
<div id="spelling-error-container">a <span aria-invalid="spelling">bcd</span> ef</div>
<div id="grammar-error-container">a <span aria-invalid="grammar">bcd</span> ef</div>
<div id="data-validation-error-container">a <span aria-invalid="true">bcd</span> ef</div>
<div id="highlight-container">a highlighted phrase ef</div>
`,
async function testTextRangeGetAttributeValue() {
// ================== UIA_FontWeightAttributeId ==================
@@ -781,7 +785,147 @@ addUiaTask(
false,
"IsReadOnly correct"
);
}
// ================== UIA_AnnotationTypesAttributeId - AnnotationType_SpellingError ==================
await runPython(`
global range
spellingErrorContainerAcc = findUiaByDomId(doc, "spelling-error-container")
range = docText.RangeFromChild(spellingErrorContainerAcc)
`);
is(await runPython(`range.GetText(-1)`), "a bcd ef", "range text correct");
info("checking mixed SpellingError properties");
ok(
await runPython(`
val = range.GetAttributeValue(UIA_AnnotationTypesAttributeId)
return val == uiaClient.ReservedMixedAttributeValue
`),
"SpellingError correct (mixed)"
);
info('Moving to aria-invalid="spelling" text run');
is(
await runPython(`range.Move(TextUnit_Format, 1)`),
1,
"Move return correct"
);
is(await runPython(`range.GetText(-1)`), "bcd", "range text correct");
info("checking SpellingError");
ok(
await runPython(`
annotations = range.GetAttributeValue(UIA_AnnotationTypesAttributeId)
return annotations == (AnnotationType_SpellingError,)
`),
"SpellingError correct"
);
// ================== UIA_AnnotationTypesAttributeId - AnnotationType_GrammarError ==================
await runPython(`
global range
grammarErrorContainerAcc = findUiaByDomId(doc, "grammar-error-container")
range = docText.RangeFromChild(grammarErrorContainerAcc)
`);
is(await runPython(`range.GetText(-1)`), "a bcd ef", "range text correct");
info("checking mixed GrammarError properties");
ok(
await runPython(`
val = range.GetAttributeValue(UIA_AnnotationTypesAttributeId)
return val == uiaClient.ReservedMixedAttributeValue
`),
"GrammarError correct (mixed)"
);
info('Moving to aria-invalid="grammar" text run');
is(
await runPython(`range.Move(TextUnit_Format, 1)`),
1,
"Move return correct"
);
is(await runPython(`range.GetText(-1)`), "bcd", "range text correct");
info("checking GrammarError");
ok(
await runPython(`
annotations = range.GetAttributeValue(UIA_AnnotationTypesAttributeId)
return annotations == (AnnotationType_GrammarError,)
`),
"GrammarError correct"
);
// ================== UIA_AnnotationTypesAttributeId - AnnotationType_DataValidationError ==================
// The IA2 -> UIA bridge does not work for aria-invalid=true or highlights.
if (gIsUiaEnabled) {
await runPython(`
global range
dataValidationErrorContainerAcc = findUiaByDomId(doc, "data-validation-error-container")
range = docText.RangeFromChild(dataValidationErrorContainerAcc)
`);
is(
await runPython(`range.GetText(-1)`),
"a bcd ef",
"range text correct"
);
info("checking mixed DataValidationError properties");
ok(
await runPython(`
val = range.GetAttributeValue(UIA_AnnotationTypesAttributeId)
return val == uiaClient.ReservedMixedAttributeValue
`),
"DataValidationError correct (mixed)"
);
info('Moving to aria-invalid="true" text run');
is(
await runPython(`range.Move(TextUnit_Format, 1)`),
1,
"Move return correct"
);
is(await runPython(`range.GetText(-1)`), "bcd", "range text correct");
info("checking DataValidationError");
ok(
await runPython(`
annotations = range.GetAttributeValue(UIA_AnnotationTypesAttributeId)
return annotations == (AnnotationType_DataValidationError,)
`),
"DataValidationError correct"
);
// ================== UIA_AnnotationTypesAttributeId - AnnotationType_Highlighted ==================
await runPython(`
global range
highlightContainerAcc = findUiaByDomId(doc, "highlight-container")
range = docText.RangeFromChild(highlightContainerAcc)
`);
is(
await runPython(`range.GetText(-1)`),
"a highlighted phrase ef",
"range text correct"
);
info("checking mixed Highlighted properties");
ok(
await runPython(`
val = range.GetAttributeValue(UIA_AnnotationTypesAttributeId)
return val == uiaClient.ReservedMixedAttributeValue
`),
"Highlighted correct (mixed)"
);
info("Moving to highlighted text run");
is(
await runPython(`range.Move(TextUnit_Format, 1)`),
1,
"Move return correct"
);
is(
await runPython(`range.GetText(-1)`),
"highlighted phrase",
"range text correct"
);
info("checking Highlighted");
ok(
await runPython(`
annotations = range.GetAttributeValue(UIA_AnnotationTypesAttributeId)
return annotations == (AnnotationType_Highlighted,)
`),
"Highlighted correct"
);
}
},
{ urlSuffix: "#:~:text=highlighted%20phrase" }
);
/**

View File

@@ -9,6 +9,7 @@
#include "nsAccUtils.h"
#include "TextLeafRange.h"
#include <comdef.h>
#include <unordered_set>
namespace mozilla::a11y {
@@ -417,6 +418,8 @@ UiaTextRange::GetAttributeValue(TEXTATTRIBUTEID aAttributeId,
MOZ_ASSERT(range.Start() <= range.End(), "Range must be valid to proceed.");
switch (aAttributeId) {
case UIA_AnnotationTypesAttributeId:
return GetAttribute<UIA_AnnotationTypesAttributeId>(range, *aRetVal);
case UIA_FontNameAttributeId:
return GetAttribute<UIA_FontNameAttributeId>(range, *aRetVal);
case UIA_FontSizeAttributeId:
@@ -721,6 +724,73 @@ UiaTextRange::GetChildren(__RPC__deref_out_opt SAFEARRAY** aRetVal) {
* AttributeTraits template specializations
*/
template <>
struct AttributeTraits<UIA_AnnotationTypesAttributeId> {
// Avoiding nsTHashSet here because it has no operator==.
using AttrType = std::unordered_set<int32_t>;
static Maybe<AttrType> GetValue(TextLeafPoint aPoint) {
// Check all of the given annotations. Build a set of the annotations that
// are present at the given TextLeafPoint.
RefPtr<AccAttributes> attrs = aPoint.GetTextAttributes();
if (!attrs) {
return {};
}
AttrType annotationsAtPoint{};
// The "invalid" atom as a key in text attributes could have value
// "spelling", "grammar", or "true". Spelling and grammar map directly to
// UIA. A non-specific "invalid" indicates a generic data validation error,
// and is mapped as such.
if (auto invalid =
attrs->GetAttribute<RefPtr<nsAtom>>(nsGkAtoms::invalid)) {
const nsAtom* invalidAtom = invalid->get();
if (invalidAtom == nsGkAtoms::spelling) {
annotationsAtPoint.insert(AnnotationType_SpellingError);
} else if (invalidAtom == nsGkAtoms::grammar) {
annotationsAtPoint.insert(AnnotationType_GrammarError);
} else if (invalidAtom == nsGkAtoms::_true) {
annotationsAtPoint.insert(AnnotationType_DataValidationError);
}
}
// The presence of the "mark" atom as a key in text attributes indicates a
// highlight at this point.
if (attrs->GetAttribute<bool>(nsGkAtoms::mark)) {
annotationsAtPoint.insert(AnnotationType_Highlighted);
}
return Some(annotationsAtPoint);
}
static AttrType DefaultValue() {
// Per UIA documentation, the default is an empty collection.
return {};
}
static HRESULT WriteToVariant(VARIANT& aVariant, const AttrType& aValue) {
SAFEARRAY* outputArr =
SafeArrayCreateVector(VT_I4, 0, static_cast<ULONG>(aValue.size()));
if (!outputArr) {
return E_OUTOFMEMORY;
}
// Copy the elements from the unordered_set to the SAFEARRAY.
LONG index = 0;
for (auto value : aValue) {
const HRESULT hr = SafeArrayPutElement(outputArr, &index, &value);
if (FAILED(hr)) {
SafeArrayDestroy(outputArr);
return hr;
}
++index;
}
aVariant.vt = VT_ARRAY | VT_I4;
aVariant.parray = outputArr;
return S_OK;
}
};
template <>
struct AttributeTraits<UIA_FontWeightAttributeId> {
using AttrType = int32_t; // LONG, but AccAttributes only accepts int32_t