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:
@@ -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" }
|
||||
);
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user