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";
|
"use strict";
|
||||||
|
|
||||||
|
/* import-globals-from ../../../mochitest/text.js */
|
||||||
|
loadScripts({ name: "text.js", dir: MOCHITESTS_DIR });
|
||||||
|
|
||||||
/* eslint-disable camelcase */
|
/* eslint-disable camelcase */
|
||||||
const SupportedTextSelection_None = 0;
|
const SupportedTextSelection_None = 0;
|
||||||
const SupportedTextSelection_Multiple = 2;
|
const SupportedTextSelection_Multiple = 2;
|
||||||
@@ -1917,3 +1920,189 @@ addUiaTask(
|
|||||||
is(top + height, docBottom, "range is at bottom of document");
|
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 "UiaTextRange.h"
|
||||||
|
|
||||||
|
#include "mozilla/a11y/HyperTextAccessibleBase.h"
|
||||||
#include "nsAccUtils.h"
|
#include "nsAccUtils.h"
|
||||||
#include "nsIAccessibleTypes.h"
|
#include "nsIAccessibleTypes.h"
|
||||||
#include "TextLeafRange.h"
|
#include "TextLeafRange.h"
|
||||||
@@ -101,6 +102,23 @@ static bool IsUiaEmbeddedObject(const Accessible* aAcc) {
|
|||||||
return true;
|
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::UiaTextRange(TextLeafRange& aRange) {
|
UiaTextRange::UiaTextRange(TextLeafRange& aRange) {
|
||||||
@@ -645,14 +663,59 @@ UiaTextRange::MoveEndpointByRange(
|
|||||||
return S_OK;
|
return S_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
STDMETHODIMP
|
// XXX Use MOZ_CAN_RUN_SCRIPT_BOUNDARY for now due to bug 1543294.
|
||||||
UiaTextRange::Select() { return E_NOTIMPL; }
|
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
|
// XXX Use MOZ_CAN_RUN_SCRIPT_BOUNDARY for now due to bug 1543294.
|
||||||
UiaTextRange::AddToSelection() { return E_NOTIMPL; }
|
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
|
// XXX Use MOZ_CAN_RUN_SCRIPT_BOUNDARY for now due to bug 1543294.
|
||||||
UiaTextRange::RemoveFromSelection() { return E_NOTIMPL; }
|
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.
|
// XXX Use MOZ_CAN_RUN_SCRIPT_BOUNDARY for now due to bug 1543294.
|
||||||
MOZ_CAN_RUN_SCRIPT_BOUNDARY STDMETHODIMP
|
MOZ_CAN_RUN_SCRIPT_BOUNDARY STDMETHODIMP
|
||||||
|
|||||||
Reference in New Issue
Block a user