Bug 1903478 part 2: Implement ITextRangeProvider's Select, AddToSelection and RemoveFromSelection methods. r=nlapre

Differential Revision: https://phabricator.services.mozilla.com/D233136
This commit is contained in:
James Teh
2025-01-07 00:29:16 +00:00
parent 8f2d340ec0
commit 327d0932a4
2 changed files with 258 additions and 6 deletions

View File

@@ -4,6 +4,9 @@
"use strict";
/* import-globals-from ../../../mochitest/text.js */
loadScripts({ name: "text.js", dir: MOCHITESTS_DIR });
/* eslint-disable camelcase */
const SupportedTextSelection_None = 0;
const SupportedTextSelection_Multiple = 2;
@@ -1917,3 +1920,189 @@ addUiaTask(
is(top + height, docBottom, "range is at bottom of document");
}
);
/**
* Test the TextRange pattern's Select method.
*/
addUiaTask(
`
<input id="input" type="text" value="ab">
<div id="contenteditable" contenteditable role="textbox">ab</div>
`,
async function testTextRangeSelect(browser, docAcc) {
// <input> and contentEditable should behave the same.
for (const id of ["input", "contenteditable"]) {
info(`Focusing ${id}`);
const acc = findAccessibleChildByID(docAcc, id, [nsIAccessibleText]);
let moved = waitForEvents([
[EVENT_FOCUS, acc],
[EVENT_TEXT_CARET_MOVED, acc],
]);
acc.takeFocus();
await moved;
info("Selecting a");
moved = waitForEvents([
[EVENT_TEXT_SELECTION_CHANGED, acc],
[EVENT_TEXT_CARET_MOVED, acc],
]);
await runPython(`
doc = getDocUia()
acc = findUiaByDomId(doc, "${id}")
text = getUiaPattern(acc, "Text")
global range
range = text.DocumentRange
range.ExpandToEnclosingUnit(TextUnit_Character)
range.Select()
`);
await moved;
testTextSelectionCount(acc, 1);
testTextGetSelection(acc, 0, 1, 0);
info("Moving caret to b");
moved = waitForEvent(EVENT_TEXT_CARET_MOVED, acc);
await runPython(`
# Collapse to b.
range.MoveEndpointByUnit(TextPatternRangeEndpoint_Start, TextUnit_Character, 1)
range.Select()
`);
await moved;
testTextSelectionCount(acc, 0);
is(acc.caretOffset, 1, "caret at 1");
}
}
);
/**
* Test the TextRange pattern's AddToSelection method.
*/
addUiaTask(
`
<input id="input" type="text" value="abc">
<div id="contenteditable" contenteditable role="textbox">abc</div>
`,
async function testTextRangeAddToSelection(browser, docAcc) {
// <input> and contentEditable should behave the same.
for (const id of ["input", "contenteditable"]) {
info(`Focusing ${id}`);
const acc = findAccessibleChildByID(docAcc, id, [nsIAccessibleText]);
let moved = waitForEvents([
[EVENT_FOCUS, acc],
[EVENT_TEXT_CARET_MOVED, acc],
]);
acc.takeFocus();
await moved;
info("Adding a to selection");
moved = waitForEvents([
[EVENT_TEXT_SELECTION_CHANGED, acc],
[EVENT_TEXT_CARET_MOVED, acc],
]);
await runPython(`
doc = getDocUia()
acc = findUiaByDomId(doc, "${id}")
text = getUiaPattern(acc, "Text")
global range
range = text.DocumentRange
range.ExpandToEnclosingUnit(TextUnit_Character)
range.AddToSelection()
`);
await moved;
testTextSelectionCount(acc, 1);
testTextGetSelection(acc, 0, 1, 0);
info("Adding c to selection");
moved = waitForEvent(EVENT_TEXT_CARET_MOVED, acc);
await runPython(`
# Move start to c.
range.MoveEndpointByUnit(TextPatternRangeEndpoint_Start, TextUnit_Character, 2)
range.ExpandToEnclosingUnit(TextUnit_Character)
range.AddToSelection()
`);
await moved;
testTextSelectionCount(acc, 2);
testTextGetSelection(acc, 0, 1, 0);
testTextGetSelection(acc, 2, 3, 1);
}
}
);
/**
* Test the TextRange pattern's RemoveFromSelection method.
*/
addUiaTask(
`
<input id="input" type="text" value="abc">
<div id="contenteditable" contenteditable role="textbox">abc</div>
`,
async function testTextRangeRemoveFromSelection(browser, docAcc) {
// <input> and contentEditable should behave the same.
for (const id of ["input", "contenteditable"]) {
info(`Focusing ${id}`);
const acc = findAccessibleChildByID(docAcc, id, [nsIAccessibleText]);
let moved = waitForEvents([
[EVENT_FOCUS, acc],
[EVENT_TEXT_CARET_MOVED, acc],
]);
acc.takeFocus();
await moved;
info("Adding a to selection");
moved = waitForEvents([
[EVENT_TEXT_SELECTION_CHANGED, acc],
[EVENT_TEXT_CARET_MOVED, acc],
]);
acc.addSelection(0, 1);
await moved;
info("Adding c to selection");
moved = waitForEvents([
[EVENT_TEXT_SELECTION_CHANGED, acc],
[EVENT_TEXT_CARET_MOVED, acc],
]);
acc.addSelection(2, 3);
await moved;
info("Removing a from selection");
moved = waitForEvents([
[EVENT_TEXT_SELECTION_CHANGED, acc],
[EVENT_TEXT_CARET_MOVED, acc],
]);
await runPython(`
doc = getDocUia()
acc = findUiaByDomId(doc, "${id}")
text = getUiaPattern(acc, "Text")
global range
range = text.DocumentRange
range.ExpandToEnclosingUnit(TextUnit_Character)
range.RemoveFromSelection()
`);
await moved;
testTextSelectionCount(acc, 1);
testTextGetSelection(acc, 2, 3, 0);
info("Removing b from selection even though it isn't selected");
await runPython(`
# Move start to b.
range.MoveEndpointByUnit(TextPatternRangeEndpoint_Start, TextUnit_Character, 1)
range.ExpandToEnclosingUnit(TextUnit_Character)
`);
await testPythonRaises(
`range.RemoveFromSelection()`,
"RemoveFromSelection failed"
);
info("Removing c from selection");
moved = waitForEvent(EVENT_TEXT_SELECTION_CHANGED, acc);
await runPython(`
# Move start to c.
range.MoveEndpointByUnit(TextPatternRangeEndpoint_Start, TextUnit_Character, 1)
range.ExpandToEnclosingUnit(TextUnit_Character)
range.RemoveFromSelection()
`);
await moved;
testTextSelectionCount(acc, 0);
}
},
// The IA2 -> UIA proxy doesn't support RemoveFromSelection correctly.
{ uiaEnabled: true, uiaDisabled: false }
);

View File

@@ -6,6 +6,7 @@
#include "UiaTextRange.h"
#include "mozilla/a11y/HyperTextAccessibleBase.h"
#include "nsAccUtils.h"
#include "nsIAccessibleTypes.h"
#include "TextLeafRange.h"
@@ -101,6 +102,23 @@ static bool IsUiaEmbeddedObject(const Accessible* aAcc) {
return true;
}
static NotNull<Accessible*> GetSelectionContainer(TextLeafRange& aRange) {
Accessible* acc = aRange.Start().mAcc;
MOZ_ASSERT(acc);
if (acc->IsTextLeaf()) {
if (Accessible* parent = acc->Parent()) {
acc = parent;
}
}
if (acc->IsTextField()) {
// Gecko uses an independent selection for <input> and <textarea>.
return WrapNotNull(acc);
}
// For everything else (including contentEditable), Gecko uses the document
// selection.
return WrapNotNull(nsAccUtils::DocumentFor(acc));
}
// UiaTextRange
UiaTextRange::UiaTextRange(TextLeafRange& aRange) {
@@ -645,14 +663,59 @@ UiaTextRange::MoveEndpointByRange(
return S_OK;
}
STDMETHODIMP
UiaTextRange::Select() { return E_NOTIMPL; }
// XXX Use MOZ_CAN_RUN_SCRIPT_BOUNDARY for now due to bug 1543294.
MOZ_CAN_RUN_SCRIPT_BOUNDARY STDMETHODIMP UiaTextRange::Select() {
TextLeafRange range = GetRange();
if (!range) {
return CO_E_OBJNOTCONNECTED;
}
NotNull<Accessible*> container = GetSelectionContainer(range);
nsTArray<TextLeafRange> ranges;
TextLeafRange::GetSelection(container, ranges);
HyperTextAccessibleBase* conHyp = container->AsHyperTextBase();
MOZ_ASSERT(conHyp);
// Remove all ranges from the selection.
for (int32_t s = ranges.Length() - 1; s >= 0; --s) {
conHyp->RemoveFromSelection(s);
}
// Select just this range.
if (!range.SetSelection(0)) {
return UIA_E_INVALIDOPERATION;
}
return S_OK;
}
STDMETHODIMP
UiaTextRange::AddToSelection() { return E_NOTIMPL; }
// XXX Use MOZ_CAN_RUN_SCRIPT_BOUNDARY for now due to bug 1543294.
MOZ_CAN_RUN_SCRIPT_BOUNDARY STDMETHODIMP UiaTextRange::AddToSelection() {
TextLeafRange range = GetRange();
if (!range) {
return CO_E_OBJNOTCONNECTED;
}
if (!range.SetSelection(-1)) {
return UIA_E_INVALIDOPERATION;
}
return S_OK;
}
STDMETHODIMP
UiaTextRange::RemoveFromSelection() { return E_NOTIMPL; }
// XXX Use MOZ_CAN_RUN_SCRIPT_BOUNDARY for now due to bug 1543294.
MOZ_CAN_RUN_SCRIPT_BOUNDARY STDMETHODIMP UiaTextRange::RemoveFromSelection() {
TextLeafRange range = GetRange();
if (!range) {
return CO_E_OBJNOTCONNECTED;
}
NotNull<Accessible*> container = GetSelectionContainer(range);
nsTArray<TextLeafRange> ranges;
TextLeafRange::GetSelection(container, ranges);
auto index = ranges.IndexOf(range);
if (index != ranges.NoIndex) {
HyperTextAccessibleBase* conHyp = container->AsHyperTextBase();
MOZ_ASSERT(conHyp);
conHyp->RemoveFromSelection(index);
return S_OK;
}
// This range isn't in the collection of selected ranges.
return UIA_E_INVALIDOPERATION;
}
// XXX Use MOZ_CAN_RUN_SCRIPT_BOUNDARY for now due to bug 1543294.
MOZ_CAN_RUN_SCRIPT_BOUNDARY STDMETHODIMP