зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1901459 part 3: Implement ITextRangeProvider::GetChildren. r=nlapre
Differential Revision: https://phabricator.services.mozilla.com/D215759
This commit is contained in:
Родитель
2451b5f8bb
Коммит
ede1495f62
|
@ -970,3 +970,52 @@ addUiaTask(
|
|||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Test the TextRange pattern's GetChildren method.
|
||||
*/
|
||||
addUiaTask(
|
||||
`<div id="editable" contenteditable role="textbox">ab <span id="cdef" role="button"><span>cd</span> <a id="ef" href="/">ef</a> </span><img id="g" src="https://example.com/a11y/accessible/tests/mochitest/moz.png" alt="g"></div>`,
|
||||
async function testTextRangeGetChildren() {
|
||||
info("Getting editable DocumentRange");
|
||||
await runPython(`
|
||||
doc = getDocUia()
|
||||
editable = findUiaByDomId(doc, "editable")
|
||||
text = getUiaPattern(editable, "Text")
|
||||
global r
|
||||
r = text.DocumentRange
|
||||
`);
|
||||
await isUiaElementArray(
|
||||
`r.GetChildren()`,
|
||||
["cdef", "g"],
|
||||
"Children are correct"
|
||||
);
|
||||
info("Expanding to word");
|
||||
await runPython(`r.ExpandToEnclosingUnit(TextUnit_Word)`);
|
||||
// Range is now "ab ".
|
||||
await isUiaElementArray(`r.GetChildren()`, [], "Children are correct");
|
||||
info("Moving 1 word");
|
||||
await runPython(`r.Move(TextUnit_Word, 1)`);
|
||||
// Range is now "cd ".
|
||||
await isUiaElementArray(`r.GetChildren()`, [], "Children are correct");
|
||||
info("Moving 1 word");
|
||||
await runPython(`r.Move(TextUnit_Word, 1)`);
|
||||
// Range is now "ef ". The range includes the link but is not completely
|
||||
// enclosed by the link.
|
||||
await isUiaElementArray(`r.GetChildren()`, ["ef"], "Children are correct");
|
||||
info("Moving end -1 character");
|
||||
await runPython(
|
||||
`r.MoveEndpointByUnit(TextPatternRangeEndpoint_End, TextUnit_Character, -1)`
|
||||
);
|
||||
// Range is now "ef". The range encloses the link, so there are no children.
|
||||
await isUiaElementArray(`r.GetChildren()`, [], "Children are correct");
|
||||
info("Moving 1 word");
|
||||
await runPython(`r.Move(TextUnit_Word, 1)`);
|
||||
// Range is now the embedded object character for the img (g). The range is
|
||||
// completely enclosed by the image.
|
||||
// The IA2 -> UIA proxy gets this wrong.
|
||||
if (gIsUiaEnabled) {
|
||||
await isUiaElementArray(`r.GetChildren()`, [], "Children are correct");
|
||||
}
|
||||
}
|
||||
);
|
||||
|
|
|
@ -58,6 +58,36 @@ static void RemoveExcludedAccessiblesFromRange(TextLeafRange& aRange) {
|
|||
}
|
||||
}
|
||||
|
||||
static bool IsUiaEmbeddedObject(const Accessible* aAcc) {
|
||||
// "For UI Automation, an embedded object is any element that has non-textual
|
||||
// boundaries such as an image, hyperlink, table, or document type"
|
||||
// https://learn.microsoft.com/en-us/windows/win32/winauto/uiauto-textpattern-and-embedded-objects-overview
|
||||
if (aAcc->IsText()) {
|
||||
return false;
|
||||
}
|
||||
switch (aAcc->Role()) {
|
||||
case roles::CONTENT_DELETION:
|
||||
case roles::CONTENT_INSERTION:
|
||||
case roles::EMPHASIS:
|
||||
case roles::LANDMARK:
|
||||
case roles::MARK:
|
||||
case roles::NAVIGATION:
|
||||
case roles::NOTE:
|
||||
case roles::PARAGRAPH:
|
||||
case roles::REGION:
|
||||
case roles::SECTION:
|
||||
case roles::STRONG:
|
||||
case roles::SUBSCRIPT:
|
||||
case roles::SUPERSCRIPT:
|
||||
case roles::TEXT:
|
||||
case roles::TEXT_CONTAINER:
|
||||
return false;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// UiaTextRange
|
||||
|
||||
UiaTextRange::UiaTextRange(TextLeafRange& aRange) {
|
||||
|
@ -454,6 +484,68 @@ UiaTextRange::GetChildren(__RPC__deref_out_opt SAFEARRAY** aRetVal) {
|
|||
return E_INVALIDARG;
|
||||
}
|
||||
*aRetVal = nullptr;
|
||||
TextLeafRange range = GetRange();
|
||||
if (!range) {
|
||||
return CO_E_OBJNOTCONNECTED;
|
||||
}
|
||||
RemoveExcludedAccessiblesFromRange(range);
|
||||
Accessible* startAcc = range.Start().mAcc;
|
||||
Accessible* endAcc = range.End().mAcc;
|
||||
Accessible* common = startAcc->GetClosestCommonInclusiveAncestor(endAcc);
|
||||
if (!common) {
|
||||
return S_OK;
|
||||
}
|
||||
// Get all the direct children of `common` from `startAcc` through `endAcc`.
|
||||
// Find the index of the direct child containing startAcc.
|
||||
int32_t startIndex = -1;
|
||||
if (startAcc == common) {
|
||||
startIndex = 0;
|
||||
} else {
|
||||
Accessible* child = startAcc;
|
||||
for (;;) {
|
||||
Accessible* parent = child->Parent();
|
||||
if (parent == common) {
|
||||
startIndex = child->IndexInParent();
|
||||
break;
|
||||
}
|
||||
child = parent;
|
||||
}
|
||||
MOZ_ASSERT(startIndex >= 0);
|
||||
}
|
||||
// Find the index of the direct child containing endAcc.
|
||||
int32_t endIndex = -1;
|
||||
if (endAcc == common) {
|
||||
endIndex = static_cast<int32_t>(common->ChildCount()) - 1;
|
||||
} else {
|
||||
Accessible* child = endAcc;
|
||||
for (;;) {
|
||||
Accessible* parent = child->Parent();
|
||||
if (parent == common) {
|
||||
endIndex = child->IndexInParent();
|
||||
break;
|
||||
}
|
||||
child = parent;
|
||||
}
|
||||
MOZ_ASSERT(endIndex >= 0);
|
||||
}
|
||||
// Now get the children between startIndex and endIndex.
|
||||
// We guess 30 children because:
|
||||
// 1. It's unlikely that a client would call GetChildren on a very large range
|
||||
// because GetChildren is normally only called when reporting content and
|
||||
// reporting the entire content of a massive range in one hit isn't ideal for
|
||||
// performance.
|
||||
// 2. A client is more likely to query the content of a line, paragraph, etc.
|
||||
// 3. It seems unlikely that there would be more than 30 children in a line or
|
||||
// paragraph, especially because we're only including children that are
|
||||
// considered embedded objects by UIA.
|
||||
AutoTArray<Accessible*, 30> children;
|
||||
for (int32_t i = startIndex; i <= endIndex; ++i) {
|
||||
Accessible* child = common->ChildAt(static_cast<uint32_t>(i));
|
||||
if (IsUiaEmbeddedObject(child)) {
|
||||
children.AppendElement(child);
|
||||
}
|
||||
}
|
||||
*aRetVal = AccessibleArrayToUiaArray(children);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
|
|
|
@ -1458,12 +1458,10 @@ long uiaRawElmProvider::GetLiveSetting() const {
|
|||
}
|
||||
|
||||
SAFEARRAY* a11y::AccessibleArrayToUiaArray(const nsTArray<Accessible*>& aAccs) {
|
||||
if (aAccs.IsEmpty()) {
|
||||
// The UIA documentation is unclear about this, but the UIA client
|
||||
// framework seems to treat a null value the same as an empty array. This
|
||||
// is also what Chromium does.
|
||||
return nullptr;
|
||||
}
|
||||
// The UIA client framework seems to treat a null value the same as an empty
|
||||
// array most of the time, but not always. In particular, Narrator breaks if
|
||||
// ITextRangeProvider::GetChildren returns null instead of an empty array.
|
||||
// Therefore, don't return null for an empty array.
|
||||
SAFEARRAY* uias = SafeArrayCreateVector(VT_UNKNOWN, 0, aAccs.Length());
|
||||
LONG indices[1] = {0};
|
||||
for (Accessible* acc : aAccs) {
|
||||
|
|
Загрузка…
Ссылка в новой задаче