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