Bug 1827557 part 2: CachedTextMarker: Differentiate between LeftLine and Line. r=eeejay

Line should return the current line when the start of the line is queried.
Otherwise, VoiceOver reports the previous line if you're on the first character of a line and you move by line with down or up arrow.

LegacyTextMarker behaves inconsistently when you use Line/LeftLine depending on whether you fetched the marker via index or from a selection range.
browser_text_basics.js treated the index behaviour as correct, but it isn't.
The tests have been updated accordingly with expected failures for non-cached.

Differential Revision: https://phabricator.services.mozilla.com/D176608
This commit is contained in:
James Teh 2023-04-27 08:27:24 +00:00
parent a431ef8f27
commit 81d78e645f
8 changed files with 135 additions and 22 deletions

View file

@ -43,6 +43,8 @@ class CachedTextMarker final {
CachedTextMarkerRange RightWordRange() const;
CachedTextMarkerRange LineRange() const;
CachedTextMarkerRange LeftLineRange() const;
CachedTextMarkerRange RightLineRange() const;

View file

@ -175,6 +175,19 @@ CachedTextMarkerRange CachedTextMarker::RightWordRange() const {
start < end ? end : start);
}
CachedTextMarkerRange CachedTextMarker::LineRange() const {
TextLeafPoint start = mPoint.FindBoundary(
nsIAccessibleText::BOUNDARY_LINE_START, eDirPrevious,
TextLeafPoint::BoundaryFlags::eStopInEditable |
TextLeafPoint::BoundaryFlags::eIgnoreListItemMarker |
TextLeafPoint::BoundaryFlags::eIncludeOrigin);
TextLeafPoint end =
start.FindBoundary(nsIAccessibleText::BOUNDARY_LINE_END, eDirNext,
TextLeafPoint::BoundaryFlags::eStopInEditable);
return CachedTextMarkerRange(start, end);
}
CachedTextMarkerRange CachedTextMarker::LeftLineRange() const {
TextLeafPoint start = mPoint.FindBoundary(
nsIAccessibleText::BOUNDARY_LINE_START, eDirPrevious,

View file

@ -63,6 +63,8 @@ class GeckoTextMarker {
GeckoTextMarkerRange RightWordRange() const;
GeckoTextMarkerRange LineRange() const;
GeckoTextMarkerRange LeftLineRange() const;
GeckoTextMarkerRange RightLineRange() const;

View file

@ -143,6 +143,15 @@ GeckoTextMarkerRange GeckoTextMarker::RightWordRange() const {
}
}
GeckoTextMarkerRange GeckoTextMarker::LineRange() const {
if (mLegacy) {
return GeckoTextMarkerRange(
mLegacyTextMarker.Range(EWhichRange::eLeftLine));
} else {
return GeckoTextMarkerRange(mCachedTextMarker.LineRange());
}
}
GeckoTextMarkerRange GeckoTextMarker::LeftLineRange() const {
if (mLegacy) {
return GeckoTextMarkerRange(

View file

@ -334,7 +334,7 @@ mozAccessible* GetEditableNativeFromGeckoAccessible(Accessible* aAcc) {
return nil;
}
return geckoTextMarker.LeftLineRange().CreateAXTextMarkerRange();
return geckoTextMarker.LineRange().CreateAXTextMarkerRange();
}
- (AXTextMarkerRangeRef)moxLeftLineTextMarkerRangeForTextMarker:

View file

@ -84,13 +84,17 @@ function testLines(
expectedLeft,
expectedRight
) {
testRangeAtMarker(
macDoc,
marker,
"AXLineTextMarkerRangeForTextMarker",
expectedLine,
`${msg}: line matches`
);
if (!isCacheEnabled && expectedLine != expectedLeft) {
todo(false, `${msg}: line broken at start with cache disabled`);
} else {
testRangeAtMarker(
macDoc,
marker,
"AXLineTextMarkerRangeForTextMarker",
expectedLine,
`${msg}: line matches`
);
}
testRangeAtMarker(
macDoc,
@ -254,14 +258,14 @@ addAccessibleTask("mac/doc_textmarker_test.html", async (browser, accDoc) => {
// Some changes in expected results when cache is enabled
// These are more correct return values (or at least not more incorrect)
expectedValues[231].words[1] = "Skip'";
expectedValues[248].lines[0] = expectedValues[248].lines[1] = "These ";
expectedValues[252].lines[0] = expectedValues[252].lines[1] = "are ";
expectedValues[255].lines[0] = expectedValues[255].lines[1] = "my ";
expectedValues[248].lines[1] = "These ";
expectedValues[252].lines[1] = "are ";
expectedValues[255].lines[1] = "my ";
expectedValues[261].words[0] = expectedValues[261].words[1] = "awards,";
expectedValues[263].lines[0] = expectedValues[263].lines[1] = "awards, ";
expectedValues[263].lines[1] = "awards, ";
expectedValues[269].words[0] = expectedValues[269].words[1] = "Mother.";
expectedValues[271].lines[0] = expectedValues[271].lines[1] = "Mother. ";
expectedValues[276].lines[0] = expectedValues[276].lines[1] = "From ";
expectedValues[271].lines[1] = "Mother. ";
expectedValues[276].lines[1] = "From ";
expectedValues[269].paragraph = "These are my awards, Mother. From Army.";
expectedValues[283].words[0] = "deceived";
expectedValues[295].paragraph = "I deceived you, mom.";

View file

@ -177,6 +177,22 @@ function testSelectionEventLeftChar(event, expectedChar) {
is(leftCharString, expectedChar, "Left character is correct");
}
function testSelectionEventLine(event, expectedLine) {
const selStart = event.macIface.getParameterizedAttributeValue(
"AXStartTextMarkerForTextMarkerRange",
event.data.AXSelectedTextMarkerRange
);
const lineRange = event.macIface.getParameterizedAttributeValue(
"AXLineTextMarkerRangeForTextMarker",
selStart
);
const lineString = event.macIface.getParameterizedAttributeValue(
"AXStringForTextMarkerRange",
lineRange
);
is(lineString, expectedLine, "Line is correct");
}
async function synthKeyAndTestValueChanged(
synthKey,
synthEvent,
@ -523,3 +539,70 @@ addAccessibleTask(
},
{ chrome: true, topLevel: true }
);
/**
* Test that the caret returns the correct line when the caret is at the start
* of the line.
*/
addAccessibleTask(
`
<textarea id="hard">ab
cd
ef</textarea>
<div id="wrapped" contenteditable style="width: 1ch;">a b c</div>
`,
async function(browser, docAcc) {
await focusIntoInput(docAcc, "hard");
let event = await synthKeyAndTestSelectionChanged(
"KEY_ArrowDown",
null,
"hard",
"",
{
AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
AXTextSelectionDirection: AXTextSelectionDirectionNext,
AXTextSelectionGranularity: AXTextSelectionGranularityLine,
}
);
testSelectionEventLine(event, "cd");
event = await synthKeyAndTestSelectionChanged(
"KEY_ArrowDown",
null,
"hard",
"",
{
AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
AXTextSelectionDirection: AXTextSelectionDirectionNext,
AXTextSelectionGranularity: AXTextSelectionGranularityLine,
}
);
testSelectionEventLine(event, "ef");
await focusIntoInput(docAcc, "wrapped");
event = await synthKeyAndTestSelectionChanged(
"KEY_ArrowDown",
null,
"wrapped",
"",
{
AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
AXTextSelectionDirection: AXTextSelectionDirectionNext,
AXTextSelectionGranularity: AXTextSelectionGranularityLine,
}
);
testSelectionEventLine(event, "b ");
event = await synthKeyAndTestSelectionChanged(
"KEY_ArrowDown",
null,
"wrapped",
"",
{
AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
AXTextSelectionDirection: AXTextSelectionDirectionNext,
AXTextSelectionGranularity: AXTextSelectionGranularityLine,
}
);
testSelectionEventLine(event, "c");
},
{ chrome: true, topLevel: true }
);

View file

@ -237,7 +237,7 @@
"Bob Loblaw Lobs Law Bomb"] },
{ style: "Bob Loblaw Lobs Law Bomb",
paragraph: "I love all of my children equally",
lines: ["Bob Loblaw Lobs Law Bomb",
lines: ["I love all of my children equally",
"Bob Loblaw Lobs Law Bomb",
"I love all of my children equally"],
words: ["Bomb", ""],
@ -470,7 +470,7 @@
element: ["AXStaticText", " equally", " equally"] },
{ style: " equally",
paragraph: "This is the best free scrapbooking class I have ever taken",
lines: ["I love all of my children equally",
lines: ["This is the best free scrapbooking class I have ever taken",
"I love all of my children equally",
"This is the best free scrapbooking class I have ever taken"],
words: ["equally", ""],
@ -928,7 +928,7 @@
"ing class I have ever taken"] },
{ style: "ing class I have ever taken",
paragraph: "\u2022 Fried cheese with club sauce",
lines: ["This is the best free scrapbooking class I have ever taken",
lines: ["\u2022 Fried cheese with club sauce",
"This is the best free scrapbooking class I have ever taken",
"\u2022 Fried cheese with club sauce"],
words: ["taken", ""],
@ -1180,7 +1180,7 @@
"\u2022 Fried cheese with club sauce"] },
{ style: "\u2022 Fried cheese with club sauce",
paragraph: "\u2022 Popcorn shrimp with club sauce",
lines: ["\u2022 Fried cheese with club sauce",
lines: ["\u2022 Popcorn shrimp with club sauce",
"\u2022 Fried cheese with club sauce",
"\u2022 Popcorn shrimp with club sauce"],
words: ["sauce", ""],
@ -1450,7 +1450,7 @@
"\u2022 Popcorn shrimp with club sauce"] },
{ style: "\u2022 Popcorn shrimp with club sauce",
paragraph: "\u2022 Chicken fingers with spicy club sauce",
lines: ["\u2022 Popcorn shrimp with club sauce",
lines: ["\u2022 Chicken fingers with spicy club sauce",
"\u2022 Popcorn shrimp with club sauce",
"\u2022 Chicken fingers with spicy club sauce"],
words: ["sauce", ""],
@ -1753,7 +1753,7 @@
element: ["AXStaticText", " club sauce", " club sauce"] },
{ style: " club sauce",
paragraph: "Do not order the Skip's Scramble",
lines: ["\u2022 Chicken fingers with spicy club sauce",
lines: ["Do not order the Skip's Scramble",
"\u2022 Chicken fingers with spicy club sauce",
"Do not order the Skip's Scramble"],
words: ["sauce", ""],
@ -2039,7 +2039,7 @@
"Do not order the Skip's Scramble"] },
{ style: "Do not order the Skip's Scramble",
paragraph: "These are my awards, Mother. From Army.",
lines: ["Do not order the Skip's Scramble",
lines: ["These ",
"Do not order the Skip's Scramble",
"These "],
words: ["Scramble", ""],
@ -2314,7 +2314,7 @@
"These are my awards, Mother. From Army."] },
{ style: "These are my awards, Mother. From Army.",
paragraph: "I deceived you, mom.",
lines: ["Army.", "Army.", "I deceived you, mom."],
lines: ["I deceived you, mom.", "Army.", "I deceived you, mom."],
words: ["Army.", ""],
element: ["AXStaticText",
"These are my awards, Mother. From Army.",