As mentioned in the bug this method does not return bounds for special insertion points. The bounds are probably correct 90% of the time, the exceptions are transition points in embedded bidi segments, among other unforseen situations. These edge cases are things we can probably live with for the time being. This addition also has a few changes: 1. HyperTextAccessible::TextBounds now returns bounds for identical start and end offsets. 2. TextLeafRange::Bounds now returns the insertion point for a collapsed range. 3. The browser caret rect test now uses the insertion point rect to compare against the caret rect. Differential Revision: https://phabricator.services.mozilla.com/D259134
668 lines
19 KiB
JavaScript
668 lines
19 KiB
JavaScript
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
"use strict";
|
|
|
|
/* import-globals-from ../../mochitest/role.js */
|
|
/* import-globals-from ../../mochitest/states.js */
|
|
loadScripts(
|
|
{ name: "role.js", dir: MOCHITESTS_DIR },
|
|
{ name: "states.js", dir: MOCHITESTS_DIR }
|
|
);
|
|
|
|
function testValueChangedEventData(
|
|
macIface,
|
|
data,
|
|
expectedId,
|
|
expectedChangeValue,
|
|
expectedEditType,
|
|
expectedWordAtLeft
|
|
) {
|
|
is(
|
|
data.AXTextChangeElement.getAttributeValue("AXDOMIdentifier"),
|
|
expectedId,
|
|
"Correct AXTextChangeElement"
|
|
);
|
|
is(
|
|
data.AXTextStateChangeType,
|
|
AXTextStateChangeTypeEdit,
|
|
"Correct AXTextStateChangeType"
|
|
);
|
|
|
|
let changeValues = data.AXTextChangeValues;
|
|
is(changeValues.length, 1, "One element in AXTextChangeValues");
|
|
is(
|
|
changeValues[0].AXTextChangeValue,
|
|
expectedChangeValue,
|
|
"Correct AXTextChangeValue"
|
|
);
|
|
is(
|
|
changeValues[0].AXTextEditType,
|
|
expectedEditType,
|
|
"Correct AXTextEditType"
|
|
);
|
|
|
|
let textMarker = changeValues[0].AXTextChangeValueStartMarker;
|
|
ok(textMarker, "There is a AXTextChangeValueStartMarker");
|
|
let range = macIface.getParameterizedAttributeValue(
|
|
"AXLeftWordTextMarkerRangeForTextMarker",
|
|
textMarker
|
|
);
|
|
let str = macIface.getParameterizedAttributeValue(
|
|
"AXStringForTextMarkerRange",
|
|
range,
|
|
"correct word before caret"
|
|
);
|
|
is(str, expectedWordAtLeft);
|
|
}
|
|
|
|
// Return true if the first given object a subset of the second
|
|
function isSubset(subset, superset) {
|
|
if (typeof subset != "object" || typeof superset != "object") {
|
|
return superset == subset;
|
|
}
|
|
|
|
for (let [prop, val] of Object.entries(subset)) {
|
|
if (!isSubset(val, superset[prop])) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
function matchWebArea(expectedId, expectedInfo) {
|
|
return (iface, data) => {
|
|
if (!data) {
|
|
return false;
|
|
}
|
|
|
|
let textChangeElemID =
|
|
data.AXTextChangeElement.getAttributeValue("AXDOMIdentifier");
|
|
|
|
return (
|
|
iface.getAttributeValue("AXRole") == "AXWebArea" &&
|
|
textChangeElemID == expectedId &&
|
|
isSubset(expectedInfo, data)
|
|
);
|
|
};
|
|
}
|
|
|
|
function matchInput(expectedId, expectedInfo) {
|
|
return (iface, data) => {
|
|
if (!data) {
|
|
return false;
|
|
}
|
|
|
|
return (
|
|
iface.getAttributeValue("AXDOMIdentifier") == expectedId &&
|
|
isSubset(expectedInfo, data)
|
|
);
|
|
};
|
|
}
|
|
|
|
async function synthKeyAndTestSelectionChanged(
|
|
synthKey,
|
|
synthEvent,
|
|
expectedId,
|
|
expectedSelectionString,
|
|
expectedSelectionInfo
|
|
) {
|
|
let selectionChangedEvents = Promise.all([
|
|
waitForMacEventWithInfo(
|
|
"AXSelectedTextChanged",
|
|
matchWebArea(expectedId, expectedSelectionInfo)
|
|
),
|
|
waitForMacEventWithInfo(
|
|
"AXSelectedTextChanged",
|
|
matchInput(expectedId, expectedSelectionInfo)
|
|
),
|
|
]);
|
|
|
|
EventUtils.synthesizeKey(synthKey, synthEvent);
|
|
let [webareaEvent, inputEvent] = await selectionChangedEvents;
|
|
is(
|
|
inputEvent.data.AXTextChangeElement.getAttributeValue("AXDOMIdentifier"),
|
|
expectedId,
|
|
"Correct AXTextChangeElement"
|
|
);
|
|
|
|
let rangeString = inputEvent.macIface.getParameterizedAttributeValue(
|
|
"AXStringForTextMarkerRange",
|
|
inputEvent.data.AXSelectedTextMarkerRange
|
|
);
|
|
is(
|
|
rangeString,
|
|
expectedSelectionString,
|
|
`selection has correct value (${expectedSelectionString})`
|
|
);
|
|
|
|
let rangeBounds = inputEvent.macIface.getParameterizedAttributeValue(
|
|
"AXBoundsForTextMarkerRange",
|
|
inputEvent.data.AXSelectedTextMarkerRange
|
|
);
|
|
|
|
ok(
|
|
rangeBounds.origin && rangeBounds.size && rangeBounds.size[0],
|
|
"Selection range has bounds"
|
|
);
|
|
|
|
is(
|
|
webareaEvent.macIface.getAttributeValue("AXDOMIdentifier"),
|
|
"body",
|
|
"Input event target is top-level WebArea"
|
|
);
|
|
rangeString = webareaEvent.macIface.getParameterizedAttributeValue(
|
|
"AXStringForTextMarkerRange",
|
|
inputEvent.data.AXSelectedTextMarkerRange
|
|
);
|
|
is(
|
|
rangeString,
|
|
expectedSelectionString,
|
|
`selection has correct value (${expectedSelectionString}) via top document`
|
|
);
|
|
|
|
return inputEvent;
|
|
}
|
|
|
|
function testSelectionEventLeftChar(event, expectedChar) {
|
|
const selStart = event.macIface.getParameterizedAttributeValue(
|
|
"AXStartTextMarkerForTextMarkerRange",
|
|
event.data.AXSelectedTextMarkerRange
|
|
);
|
|
const selLeft = event.macIface.getParameterizedAttributeValue(
|
|
"AXPreviousTextMarkerForTextMarker",
|
|
selStart
|
|
);
|
|
const leftCharRange = event.macIface.getParameterizedAttributeValue(
|
|
"AXTextMarkerRangeForUnorderedTextMarkers",
|
|
[selLeft, selStart]
|
|
);
|
|
const leftCharString = event.macIface.getParameterizedAttributeValue(
|
|
"AXStringForTextMarkerRange",
|
|
leftCharRange
|
|
);
|
|
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,
|
|
expectedId,
|
|
expectedTextSelectionId,
|
|
expectedChangeValue,
|
|
expectedEditType,
|
|
expectedWordAtLeft
|
|
) {
|
|
let valueChangedEvents = Promise.all([
|
|
waitForMacEvent(
|
|
"AXSelectedTextChanged",
|
|
matchWebArea(expectedTextSelectionId, {
|
|
AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
|
|
})
|
|
),
|
|
waitForMacEvent(
|
|
"AXSelectedTextChanged",
|
|
matchInput(expectedTextSelectionId, {
|
|
AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
|
|
})
|
|
),
|
|
waitForMacEventWithInfo(
|
|
"AXValueChanged",
|
|
matchWebArea(expectedId, {
|
|
AXTextStateChangeType: AXTextStateChangeTypeEdit,
|
|
AXTextChangeValues: [
|
|
{
|
|
AXTextChangeValue: expectedChangeValue,
|
|
AXTextEditType: expectedEditType,
|
|
},
|
|
],
|
|
})
|
|
),
|
|
waitForMacEventWithInfo(
|
|
"AXValueChanged",
|
|
matchInput(expectedId, {
|
|
AXTextStateChangeType: AXTextStateChangeTypeEdit,
|
|
AXTextChangeValues: [
|
|
{
|
|
AXTextChangeValue: expectedChangeValue,
|
|
AXTextEditType: expectedEditType,
|
|
},
|
|
],
|
|
})
|
|
),
|
|
]);
|
|
|
|
EventUtils.synthesizeKey(synthKey, synthEvent);
|
|
let [, , webareaEvent, inputEvent] = await valueChangedEvents;
|
|
|
|
testValueChangedEventData(
|
|
webareaEvent.macIface,
|
|
webareaEvent.data,
|
|
expectedId,
|
|
expectedChangeValue,
|
|
expectedEditType,
|
|
expectedWordAtLeft
|
|
);
|
|
testValueChangedEventData(
|
|
inputEvent.macIface,
|
|
inputEvent.data,
|
|
expectedId,
|
|
expectedChangeValue,
|
|
expectedEditType,
|
|
expectedWordAtLeft
|
|
);
|
|
}
|
|
|
|
async function focusIntoInput(accDoc, inputId, innerContainerId) {
|
|
let selectionId = innerContainerId ? innerContainerId : inputId;
|
|
let input = getNativeInterface(accDoc, inputId);
|
|
ok(!input.getAttributeValue("AXFocused"), "input is not focused");
|
|
ok(input.isAttributeSettable("AXFocused"), "input is focusable");
|
|
let events = Promise.all([
|
|
waitForMacEvent(
|
|
"AXFocusedUIElementChanged",
|
|
iface => iface.getAttributeValue("AXDOMIdentifier") == inputId
|
|
),
|
|
waitForMacEventWithInfo(
|
|
"AXSelectedTextChanged",
|
|
matchWebArea(selectionId, {
|
|
AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
|
|
})
|
|
),
|
|
waitForMacEventWithInfo(
|
|
"AXSelectedTextChanged",
|
|
matchInput(selectionId, {
|
|
AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
|
|
})
|
|
),
|
|
]);
|
|
input.setAttributeValue("AXFocused", true);
|
|
await events;
|
|
}
|
|
|
|
async function focusIntoInputAndType(accDoc, inputId, innerContainerId) {
|
|
let selectionId = innerContainerId ? innerContainerId : inputId;
|
|
await focusIntoInput(accDoc, inputId, innerContainerId);
|
|
|
|
async function testTextInput(
|
|
synthKey,
|
|
expectedChangeValue,
|
|
expectedWordAtLeft
|
|
) {
|
|
await synthKeyAndTestValueChanged(
|
|
synthKey,
|
|
null,
|
|
inputId,
|
|
selectionId,
|
|
expectedChangeValue,
|
|
AXTextEditTypeTyping,
|
|
expectedWordAtLeft
|
|
);
|
|
}
|
|
|
|
await testTextInput("h", "h", "h");
|
|
await testTextInput("e", "e", "he");
|
|
await testTextInput("l", "l", "hel");
|
|
await testTextInput("l", "l", "hell");
|
|
await testTextInput("o", "o", "hello");
|
|
await testTextInput(" ", " ", "hello");
|
|
// You would expect this to be useless but this is what VO
|
|
// consumes. I guess it concats the inserted text data to the
|
|
// word to the left of the marker.
|
|
await testTextInput("w", "w", " ");
|
|
await testTextInput("o", "o", "wo");
|
|
await testTextInput("r", "r", "wor");
|
|
await testTextInput("l", "l", "worl");
|
|
await testTextInput("d", "d", "world");
|
|
|
|
async function testTextDelete(expectedChangeValue, expectedWordAtLeft) {
|
|
await synthKeyAndTestValueChanged(
|
|
"KEY_Backspace",
|
|
null,
|
|
inputId,
|
|
selectionId,
|
|
expectedChangeValue,
|
|
AXTextEditTypeDelete,
|
|
expectedWordAtLeft
|
|
);
|
|
}
|
|
|
|
await testTextDelete("d", "worl");
|
|
await testTextDelete("l", "wor");
|
|
|
|
await synthKeyAndTestSelectionChanged(
|
|
"KEY_ArrowLeft",
|
|
null,
|
|
selectionId,
|
|
"",
|
|
{
|
|
AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
|
|
AXTextSelectionDirection: AXTextSelectionDirectionPrevious,
|
|
AXTextSelectionGranularity: AXTextSelectionGranularityCharacter,
|
|
}
|
|
);
|
|
await synthKeyAndTestSelectionChanged(
|
|
"KEY_ArrowLeft",
|
|
{ shiftKey: true },
|
|
selectionId,
|
|
"o",
|
|
{
|
|
AXTextStateChangeType: AXTextStateChangeTypeSelectionExtend,
|
|
AXTextSelectionDirection: AXTextSelectionDirectionPrevious,
|
|
AXTextSelectionGranularity: AXTextSelectionGranularityCharacter,
|
|
}
|
|
);
|
|
await synthKeyAndTestSelectionChanged(
|
|
"KEY_ArrowLeft",
|
|
{ shiftKey: true },
|
|
selectionId,
|
|
"wo",
|
|
{
|
|
AXTextStateChangeType: AXTextStateChangeTypeSelectionExtend,
|
|
AXTextSelectionDirection: AXTextSelectionDirectionPrevious,
|
|
AXTextSelectionGranularity: AXTextSelectionGranularityCharacter,
|
|
}
|
|
);
|
|
await synthKeyAndTestSelectionChanged(
|
|
"KEY_ArrowLeft",
|
|
null,
|
|
selectionId,
|
|
"",
|
|
{ AXTextStateChangeType: AXTextStateChangeTypeSelectionMove }
|
|
);
|
|
await synthKeyAndTestSelectionChanged(
|
|
"KEY_ArrowLeft",
|
|
{ shiftKey: true, metaKey: true },
|
|
selectionId,
|
|
"hello ",
|
|
{
|
|
AXTextStateChangeType: AXTextStateChangeTypeSelectionExtend,
|
|
AXTextSelectionDirection: AXTextSelectionDirectionBeginning,
|
|
AXTextSelectionGranularity: AXTextSelectionGranularityLine,
|
|
}
|
|
);
|
|
await synthKeyAndTestSelectionChanged(
|
|
"KEY_ArrowLeft",
|
|
null,
|
|
selectionId,
|
|
"",
|
|
{ AXTextStateChangeType: AXTextStateChangeTypeSelectionMove }
|
|
);
|
|
await synthKeyAndTestSelectionChanged(
|
|
"KEY_ArrowRight",
|
|
{ shiftKey: true, altKey: true },
|
|
selectionId,
|
|
"hello",
|
|
{
|
|
AXTextStateChangeType: AXTextStateChangeTypeSelectionExtend,
|
|
AXTextSelectionDirection: AXTextSelectionDirectionNext,
|
|
AXTextSelectionGranularity: AXTextSelectionGranularityWord,
|
|
}
|
|
);
|
|
}
|
|
|
|
// Test text input
|
|
addAccessibleTask(
|
|
`<a href="#">link</a> <input id="input">`,
|
|
async (browser, accDoc) => {
|
|
await focusIntoInputAndType(accDoc, "input");
|
|
},
|
|
{ topLevel: true, iframe: true, remoteIframe: true }
|
|
);
|
|
|
|
// Test content editable
|
|
addAccessibleTask(
|
|
`<div id="input" contentEditable="true" tabindex="0" role="textbox" aria-multiline="true"><div id="inner"><br /></div></div>`,
|
|
async (browser, accDoc) => {
|
|
const inner = getNativeInterface(accDoc, "inner");
|
|
const editableAncestor = inner.getAttributeValue("AXEditableAncestor");
|
|
is(
|
|
editableAncestor.getAttributeValue("AXDOMIdentifier"),
|
|
"input",
|
|
"Editable ancestor is input"
|
|
);
|
|
await focusIntoInputAndType(accDoc, "input");
|
|
}
|
|
);
|
|
|
|
// Test input that gets role::EDITCOMBOBOX
|
|
addAccessibleTask(`<input type="text" id="box">`, async (browser, accDoc) => {
|
|
const box = getNativeInterface(accDoc, "box");
|
|
const editableAncestor = box.getAttributeValue("AXEditableAncestor");
|
|
is(
|
|
editableAncestor.getAttributeValue("AXDOMIdentifier"),
|
|
"box",
|
|
"Editable ancestor is box itself"
|
|
);
|
|
await focusIntoInputAndType(accDoc, "box");
|
|
});
|
|
|
|
// Test multiline caret control in a text area
|
|
addAccessibleTask(
|
|
`<textarea id="input" cols="15">one two three four five six seven eight</textarea>`,
|
|
async (browser, accDoc) => {
|
|
await focusIntoInput(accDoc, "input");
|
|
|
|
await synthKeyAndTestSelectionChanged("KEY_ArrowRight", null, "input", "", {
|
|
AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
|
|
AXTextSelectionDirection: AXTextSelectionDirectionNext,
|
|
AXTextSelectionGranularity: AXTextSelectionGranularityCharacter,
|
|
});
|
|
|
|
await synthKeyAndTestSelectionChanged("KEY_ArrowDown", null, "input", "", {
|
|
AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
|
|
AXTextSelectionDirection: AXTextSelectionDirectionNext,
|
|
AXTextSelectionGranularity: AXTextSelectionGranularityLine,
|
|
});
|
|
|
|
await synthKeyAndTestSelectionChanged(
|
|
"KEY_ArrowLeft",
|
|
{ metaKey: true },
|
|
"input",
|
|
"",
|
|
{
|
|
AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
|
|
AXTextSelectionDirection: AXTextSelectionDirectionBeginning,
|
|
AXTextSelectionGranularity: AXTextSelectionGranularityLine,
|
|
}
|
|
);
|
|
|
|
await synthKeyAndTestSelectionChanged(
|
|
"KEY_ArrowRight",
|
|
{ metaKey: true },
|
|
"input",
|
|
"",
|
|
{
|
|
AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
|
|
AXTextSelectionDirection: AXTextSelectionDirectionEnd,
|
|
AXTextSelectionGranularity: AXTextSelectionGranularityLine,
|
|
}
|
|
);
|
|
},
|
|
{ topLevel: true, iframe: true, remoteIframe: true }
|
|
);
|
|
|
|
/**
|
|
* Test that the caret returns the correct marker when it is positioned after
|
|
* the last character (to facilitate appending text).
|
|
*/
|
|
addAccessibleTask(
|
|
`<input id="input" value="abc">`,
|
|
async function (browser, docAcc) {
|
|
await focusIntoInput(docAcc, "input");
|
|
|
|
let event = await synthKeyAndTestSelectionChanged(
|
|
"KEY_ArrowRight",
|
|
null,
|
|
"input",
|
|
"",
|
|
{
|
|
AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
|
|
AXTextSelectionDirection: AXTextSelectionDirectionNext,
|
|
AXTextSelectionGranularity: AXTextSelectionGranularityCharacter,
|
|
}
|
|
);
|
|
testSelectionEventLeftChar(event, "a");
|
|
event = await synthKeyAndTestSelectionChanged(
|
|
"KEY_ArrowRight",
|
|
null,
|
|
"input",
|
|
"",
|
|
{
|
|
AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
|
|
AXTextSelectionDirection: AXTextSelectionDirectionNext,
|
|
AXTextSelectionGranularity: AXTextSelectionGranularityCharacter,
|
|
}
|
|
);
|
|
testSelectionEventLeftChar(event, "b");
|
|
event = await synthKeyAndTestSelectionChanged(
|
|
"KEY_ArrowRight",
|
|
null,
|
|
"input",
|
|
"",
|
|
{
|
|
AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
|
|
AXTextSelectionDirection: AXTextSelectionDirectionNext,
|
|
AXTextSelectionGranularity: AXTextSelectionGranularityCharacter,
|
|
}
|
|
);
|
|
testSelectionEventLeftChar(event, "c");
|
|
},
|
|
{ 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
|
|
|
|
gh
|
|
</textarea>
|
|
<div role="textbox" id="wrapped" contenteditable style="width: 1ch;">a b c</div>
|
|
`,
|
|
async function (browser, docAcc) {
|
|
let hard = getNativeInterface(docAcc, "hard");
|
|
await focusIntoInput(docAcc, "hard");
|
|
is(hard.getAttributeValue("AXInsertionPointLineNumber"), 0);
|
|
let event = await synthKeyAndTestSelectionChanged(
|
|
"KEY_ArrowDown",
|
|
null,
|
|
"hard",
|
|
"",
|
|
{
|
|
AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
|
|
AXTextSelectionDirection: AXTextSelectionDirectionNext,
|
|
AXTextSelectionGranularity: AXTextSelectionGranularityLine,
|
|
}
|
|
);
|
|
testSelectionEventLine(event, "cd");
|
|
is(hard.getAttributeValue("AXInsertionPointLineNumber"), 1);
|
|
event = await synthKeyAndTestSelectionChanged(
|
|
"KEY_ArrowDown",
|
|
null,
|
|
"hard",
|
|
"",
|
|
{
|
|
AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
|
|
AXTextSelectionDirection: AXTextSelectionDirectionNext,
|
|
AXTextSelectionGranularity: AXTextSelectionGranularityLine,
|
|
}
|
|
);
|
|
testSelectionEventLine(event, "ef");
|
|
is(hard.getAttributeValue("AXInsertionPointLineNumber"), 2);
|
|
event = await synthKeyAndTestSelectionChanged(
|
|
"KEY_ArrowDown",
|
|
null,
|
|
"hard",
|
|
"",
|
|
{
|
|
AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
|
|
AXTextSelectionDirection: AXTextSelectionDirectionNext,
|
|
AXTextSelectionGranularity: AXTextSelectionGranularityLine,
|
|
}
|
|
);
|
|
testSelectionEventLine(event, "");
|
|
is(hard.getAttributeValue("AXInsertionPointLineNumber"), 3);
|
|
event = await synthKeyAndTestSelectionChanged(
|
|
"KEY_ArrowDown",
|
|
null,
|
|
"hard",
|
|
"",
|
|
{
|
|
AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
|
|
AXTextSelectionDirection: AXTextSelectionDirectionNext,
|
|
AXTextSelectionGranularity: AXTextSelectionGranularityLine,
|
|
}
|
|
);
|
|
testSelectionEventLine(event, "gh");
|
|
is(hard.getAttributeValue("AXInsertionPointLineNumber"), 4);
|
|
event = await synthKeyAndTestSelectionChanged(
|
|
"KEY_ArrowDown",
|
|
null,
|
|
"hard",
|
|
"",
|
|
{
|
|
AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
|
|
AXTextSelectionDirection: AXTextSelectionDirectionNext,
|
|
AXTextSelectionGranularity: AXTextSelectionGranularityLine,
|
|
}
|
|
);
|
|
testSelectionEventLine(event, "");
|
|
is(hard.getAttributeValue("AXInsertionPointLineNumber"), 5);
|
|
|
|
let wrapped = getNativeInterface(docAcc, "wrapped");
|
|
await focusIntoInput(docAcc, "wrapped");
|
|
is(wrapped.getAttributeValue("AXInsertionPointLineNumber"), 0);
|
|
event = await synthKeyAndTestSelectionChanged(
|
|
"KEY_ArrowDown",
|
|
null,
|
|
"wrapped",
|
|
"",
|
|
{
|
|
AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
|
|
AXTextSelectionDirection: AXTextSelectionDirectionNext,
|
|
AXTextSelectionGranularity: AXTextSelectionGranularityLine,
|
|
}
|
|
);
|
|
testSelectionEventLine(event, "b ");
|
|
is(wrapped.getAttributeValue("AXInsertionPointLineNumber"), 1);
|
|
event = await synthKeyAndTestSelectionChanged(
|
|
"KEY_ArrowDown",
|
|
null,
|
|
"wrapped",
|
|
"",
|
|
{
|
|
AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
|
|
AXTextSelectionDirection: AXTextSelectionDirectionNext,
|
|
AXTextSelectionGranularity: AXTextSelectionGranularityLine,
|
|
}
|
|
);
|
|
testSelectionEventLine(event, "c");
|
|
is(wrapped.getAttributeValue("AXInsertionPointLineNumber"), 2);
|
|
},
|
|
{ chrome: true, topLevel: true }
|
|
);
|