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 RightWordRange() const;
CachedTextMarkerRange LineRange() const;
CachedTextMarkerRange LeftLineRange() const; CachedTextMarkerRange LeftLineRange() const;
CachedTextMarkerRange RightLineRange() const; CachedTextMarkerRange RightLineRange() const;

View file

@ -175,6 +175,19 @@ CachedTextMarkerRange CachedTextMarker::RightWordRange() const {
start < end ? end : start); 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 { CachedTextMarkerRange CachedTextMarker::LeftLineRange() const {
TextLeafPoint start = mPoint.FindBoundary( TextLeafPoint start = mPoint.FindBoundary(
nsIAccessibleText::BOUNDARY_LINE_START, eDirPrevious, nsIAccessibleText::BOUNDARY_LINE_START, eDirPrevious,

View file

@ -63,6 +63,8 @@ class GeckoTextMarker {
GeckoTextMarkerRange RightWordRange() const; GeckoTextMarkerRange RightWordRange() const;
GeckoTextMarkerRange LineRange() const;
GeckoTextMarkerRange LeftLineRange() const; GeckoTextMarkerRange LeftLineRange() const;
GeckoTextMarkerRange RightLineRange() 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 { GeckoTextMarkerRange GeckoTextMarker::LeftLineRange() const {
if (mLegacy) { if (mLegacy) {
return GeckoTextMarkerRange( return GeckoTextMarkerRange(

View file

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

View file

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

View file

@ -177,6 +177,22 @@ function testSelectionEventLeftChar(event, expectedChar) {
is(leftCharString, expectedChar, "Left character is correct"); 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( async function synthKeyAndTestValueChanged(
synthKey, synthKey,
synthEvent, synthEvent,
@ -523,3 +539,70 @@ addAccessibleTask(
}, },
{ chrome: true, topLevel: true } { 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"] }, "Bob Loblaw Lobs Law Bomb"] },
{ style: "Bob Loblaw Lobs Law Bomb", { style: "Bob Loblaw Lobs Law Bomb",
paragraph: "I love all of my children equally", 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", "Bob Loblaw Lobs Law Bomb",
"I love all of my children equally"], "I love all of my children equally"],
words: ["Bomb", ""], words: ["Bomb", ""],
@ -470,7 +470,7 @@
element: ["AXStaticText", " equally", " equally"] }, element: ["AXStaticText", " equally", " equally"] },
{ style: " equally", { style: " equally",
paragraph: "This is the best free scrapbooking class I have ever taken", 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", "I love all of my children equally",
"This is the best free scrapbooking class I have ever taken"], "This is the best free scrapbooking class I have ever taken"],
words: ["equally", ""], words: ["equally", ""],
@ -928,7 +928,7 @@
"ing class I have ever taken"] }, "ing class I have ever taken"] },
{ style: "ing class I have ever taken", { style: "ing class I have ever taken",
paragraph: "\u2022 Fried cheese with club sauce", 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", "This is the best free scrapbooking class I have ever taken",
"\u2022 Fried cheese with club sauce"], "\u2022 Fried cheese with club sauce"],
words: ["taken", ""], words: ["taken", ""],
@ -1180,7 +1180,7 @@
"\u2022 Fried cheese with club sauce"] }, "\u2022 Fried cheese with club sauce"] },
{ style: "\u2022 Fried cheese with club sauce", { style: "\u2022 Fried cheese with club sauce",
paragraph: "\u2022 Popcorn shrimp 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 Fried cheese with club sauce",
"\u2022 Popcorn shrimp with club sauce"], "\u2022 Popcorn shrimp with club sauce"],
words: ["sauce", ""], words: ["sauce", ""],
@ -1450,7 +1450,7 @@
"\u2022 Popcorn shrimp with club sauce"] }, "\u2022 Popcorn shrimp with club sauce"] },
{ style: "\u2022 Popcorn shrimp with club sauce", { style: "\u2022 Popcorn shrimp with club sauce",
paragraph: "\u2022 Chicken fingers with spicy 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 Popcorn shrimp with club sauce",
"\u2022 Chicken fingers with spicy club sauce"], "\u2022 Chicken fingers with spicy club sauce"],
words: ["sauce", ""], words: ["sauce", ""],
@ -1753,7 +1753,7 @@
element: ["AXStaticText", " club sauce", " club sauce"] }, element: ["AXStaticText", " club sauce", " club sauce"] },
{ style: " club sauce", { style: " club sauce",
paragraph: "Do not order the Skip's Scramble", 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", "\u2022 Chicken fingers with spicy club sauce",
"Do not order the Skip's Scramble"], "Do not order the Skip's Scramble"],
words: ["sauce", ""], words: ["sauce", ""],
@ -2039,7 +2039,7 @@
"Do not order the Skip's Scramble"] }, "Do not order the Skip's Scramble"] },
{ style: "Do not order the Skip's Scramble", { style: "Do not order the Skip's Scramble",
paragraph: "These are my awards, Mother. From Army.", 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", "Do not order the Skip's Scramble",
"These "], "These "],
words: ["Scramble", ""], words: ["Scramble", ""],
@ -2314,7 +2314,7 @@
"These are my awards, Mother. From Army."] }, "These are my awards, Mother. From Army."] },
{ style: "These are my awards, Mother. From Army.", { style: "These are my awards, Mother. From Army.",
paragraph: "I deceived you, mom.", paragraph: "I deceived you, mom.",
lines: ["Army.", "Army.", "I deceived you, mom."], lines: ["I deceived you, mom.", "Army.", "I deceived you, mom."],
words: ["Army.", ""], words: ["Army.", ""],
element: ["AXStaticText", element: ["AXStaticText",
"These are my awards, Mother. From Army.", "These are my awards, Mother. From Army.",