<!DOCTYPE html>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<p id="paragraph">
The <b id="quick">quick</b> brown fox<br>
jumps over the lazy <b id="dog">dog</b>.
</p>
<div contentEditable="true" id="contentEditable">
<div>
<span>Line </span><span>one has one trailing space.</span><span> </span>
</div>
<div>
<span>		</span><span>Line</span><span> two has two leading tabs.</span>
</div>
</div>
<p id="paragraphWithLink">
Paragraph with a <a href="#">link</a> inside.
</p>
<ol id="list">
<li>List item</li>
<li><button aria-label="button"></button></li>
</ol>
<p id="paragraphWithAnonymousInline">
Paragraph with an <span style="background-color: red;">anonymous inline</span> inside.
</p>
<!-- Adding "outline-style" on the <span> ensures that its inline line boxes are
not culled. -->
<div id="multilineInline" contenteditable="true">
This is a
<span style="outline-style: solid">
test with a<br>
second
</span>
line.
</div>
<div id="anonymousMultilineInline" contenteditable="true">
This is a
<span aria-invalid="grammar">
test with a<br>
second
</span>
line.
</div>
<script>
test(() => {
let axObj = accessibilityController.accessibleElementById('paragraph');
// Find the first inline text box.
while (axObj.childrenCount > 0)
axObj = axObj.childAtIndex(0);
assert_equals(axObj.role, 'AXRole: AXInlineTextBox');
let lineText = [];
let lastInlineText = axObj;
while (axObj && axObj.isValid) {
assert_equals(axObj.role, 'AXRole: AXInlineTextBox');
lineText.push(axObj.name.replace('AXValue: ', ''));
lastInlineText = axObj;
axObj = axObj.nextOnLine();
}
assert_array_equals(lineText, ['The ', 'quick', ' brown fox', '\n']);
// Now walk backwards.
lineText = [];
axObj = lastInlineText;
while (axObj && axObj.isValid) {
assert_equals(axObj.role, 'AXRole: AXInlineTextBox');
lineText.unshift(axObj.name.replace('AXValue: ', ''));
axObj = axObj.previousOnLine();
}
assert_array_equals(lineText, ['The ', 'quick', ' brown fox', '\n']);
}, 'Test |NextOnLine| and |PreviousOnLine| on |AXInlineTextBox|.');
test(() => {
let axEditable = accessibilityController.accessibleElementById('contentEditable');
// There should be two lines in this content editable.
assert_equals(axEditable.childrenCount, 2);
let lines = [axEditable.childAtIndex(0), axEditable.childAtIndex(1)];
let lineText = [
['Line ', 'one has one trailing space.'],
['Line', ' two has two leading tabs.']
];
for (let i = 0; i < lines.length; ++i) {
let currentLine = lines[i];
assert_equals(currentLine.nextOnLine(), undefined);
assert_equals(currentLine.previousOnLine(), undefined);
}
for (let i = 0; i < lines.length; ++i) {
let currentLine = lines[i];
let currentLineText = lineText[i];
// There should be two spans per line since white space is removed.
assert_equals(currentLine.childrenCount, 2);
let span1 = currentLine.childAtIndex(0);
let span2 = currentLine.childAtIndex(1);
let inlineText1 = span1.childAtIndex(0);
let inlineText2 = span2.childAtIndex(0);
let spanText1 = currentLineText[0];
let spanText2 = currentLineText[1];
assert_equals(span1.role, 'AXRole: AXStaticText');
assert_equals(span2.role, 'AXRole: AXStaticText');
assert_equals(span1.name, spanText1);
assert_equals(span2.name, spanText2);
// |next/previousOnLine| APIs jump directly to the inline text boxes
// skipping the parent span element.
assert_equals(span1.nextOnLine(), inlineText2, 'span1 -> inlineText2');
assert_equals(inlineText1.nextOnLine(), inlineText2, 'inlineText1 -> inlineText2');
assert_equals(span2.previousOnLine(), inlineText1, 'span2 -> inlineText1');
assert_equals(inlineText2.previousOnLine(), inlineText1, 'inlineText2 -> inlineText1');
}
}, 'Test |NextOnLine| and |PreviousOnLine| on |AXLayoutObject|.');
test(() => {
let axObj = accessibilityController.accessibleElementById('paragraphWithLink');
// There should be two static text children and a link in this paragraph.
assert_equals(axObj.childrenCount, 3);
axObj = axObj.childAtIndex(0);
assert_equals(axObj.role, 'AXRole: AXStaticText');
let lineText = [];
lineText.push(axObj.name);
for (let i = 0; i < 2; ++i) {
axObj = axObj.nextOnLine();
assert_equals(axObj.role, 'AXRole: AXInlineTextBox');
lineText.push(axObj.name);
}
assert_equals(axObj.nextOnLine(), undefined);
for (let i = 0; i < 2; ++i) {
axObj = axObj.previousOnLine();
assert_equals(axObj.role, 'AXRole: AXInlineTextBox');
lineText.push(axObj.name);
}
assert_equals(axObj.previousOnLine(), undefined);
assert_array_equals(lineText, ['Paragraph with a ', 'link', ' inside.',
'link', 'Paragraph with a ']);
}, 'Test |NextOnLine| and |PreviousOnLine| on paragraphs with links.');
test(() => {
let axObj = accessibilityController.accessibleElementById('paragraphWithAnonymousInline');
// There should be three static text children in this paragraph.
assert_equals(axObj.childrenCount, 3);
axObj = axObj.childAtIndex(0);
assert_equals(axObj.role, 'AXRole: AXStaticText');
let lineText = [];
lineText.push(axObj.name);
for (let i = 0; i < 2; ++i) {
axObj = axObj.nextOnLine();
assert_equals(axObj.role, 'AXRole: AXInlineTextBox');
lineText.push(axObj.name);
}
assert_equals(axObj.nextOnLine(), undefined);
for (let i = 0; i < 2; ++i) {
axObj = axObj.previousOnLine();
assert_equals(axObj.role, 'AXRole: AXInlineTextBox');
lineText.push(axObj.name);
}
assert_equals(axObj.previousOnLine(), undefined);
assert_array_equals(lineText, ['Paragraph with an ', 'anonymous inline', ' inside.',
'anonymous inline', 'Paragraph with an ']);
}, 'Test |NextOnLine| and |PreviousOnLine| on paragraphs with anonymous blocks.');
test(() => {
let axObj = accessibilityController.accessibleElementById('list');
// There should be two list items in this list.
assert_equals(axObj.childrenCount, 2);
axObj = axObj.childAtIndex(0);
assert_equals(axObj.role, 'AXRole: AXListItem');
// There should be a list marker and some text in this list item.
assert_equals(axObj.childrenCount, 2);
axObj = axObj.childAtIndex(0);
assert_equals(axObj.role, 'AXRole: AXListMarker');
let lineText = [];
lineText.push(axObj.name);
axObj = axObj.nextOnLine();
assert_equals(axObj.role, 'AXRole: AXInlineTextBox');
lineText.push(axObj.name);
// Both the inline text box, as well as its static text parent, should be pointing to the same "previousOnLine".
assert_equals(axObj.parentElement().previousOnLine(), axObj.previousOnLine());
axObj = axObj.previousOnLine();
axObj = axObj.parentElement();
assert_equals(axObj.role, 'AXRole: AXListMarker');
lineText.push(axObj.name);
assert_array_equals(lineText, ['1. ', 'List item', '1. ']);
axObj = accessibilityController.accessibleElementById('list');
axObj = axObj.childAtIndex(1);
assert_equals(axObj.role, 'AXRole: AXListItem');
// There should be a list marker and a button in this list item.
assert_equals(axObj.childrenCount, 2);
axObj = axObj.childAtIndex(0);
assert_equals(axObj.role, 'AXRole: AXListMarker');
lineText = [];
lineText.push(axObj.name);
axObj = axObj.nextOnLine();
assert_equals(axObj.role, 'AXRole: AXButton');
lineText.push(axObj.name);
axObj = axObj.previousOnLine();
axObj = axObj.parentElement();
assert_equals(axObj.role, 'AXRole: AXListMarker');
lineText.push(axObj.name);
assert_array_equals(lineText, ['2. ', 'button', '2. ']);
}, 'Test |NextOnLine| and |PreviousOnLine| on list markers.');
test(() => {
let axObj = accessibilityController.accessibleElementById('multilineInline');
// The tree is:
// [0] AXStaticText "This is a "
// AXInlineTextBox "This is a "
// [1] AXStaticText "test with a"
// AXInlineTextBox "test with a"
// [2] AXLineBreak "\n"
// AXInlineTextBox "\n"
// [3] AXStaticText "second "
// AXInlineTextBox "second "
// [4] AXStaticText "line."
// AXInlineTextBox "line."
// There should be 5 static text children in this contenteditable.
// The span is not in the accessibility tree at all.
assert_equals(axObj.childrenCount, 5);
const lineBreak = axObj.childAtIndex(2);
assert_equals(lineBreak.role, 'AXRole: AXLineBreak');
axObj = axObj.childAtIndex(0);
assert_equals(axObj.role, 'AXRole: AXStaticText');
//
// Test first line.
//
let lineText = [];
lineText.push(axObj.name);
// Both the static text object as well as its only inline text box child,
// should be connected to the same inline text box as their "nextOnLine".
assert_equals(axObj.childrenCount, 1);
assert_equals(axObj.childAtIndex(0).role, 'AXRole: AXInlineTextBox');
assert_equals(axObj.childAtIndex(0).nextOnLine(), axObj.nextOnLine());
axObj = axObj.nextOnLine();
assert_equals(axObj.role, 'AXRole: AXInlineTextBox');
lineText.push(axObj.name);
assert_equals(axObj.parentElement().nextOnLine(), lineBreak.childAtIndex(0),
'"text with a".name.parentElement().nextOnLine()');
assert_equals(axObj.nextOnLine(), lineBreak.childAtIndex(0),
'"text with a".name.nextOnLine()');
assert_equals(axObj.parentElement().previousOnLine(), axObj.previousOnLine());
axObj = axObj.previousOnLine();
assert_equals(axObj.role, 'AXRole: AXInlineTextBox');
lineText.push(axObj.name);
assert_equals(axObj.parentElement().previousOnLine(), undefined,
'"This is a ".name.parentElement().nextOnLine()');
assert_equals(axObj.previousOnLine(), undefined,
'"This is a ".name.nextOnLine()');
assert_array_equals(lineText, ['This is a ', 'test with a', 'This is a ']);
//
// Test second line.
//
axObj = accessibilityController.accessibleElementById('multilineInline');
axObj = axObj.childAtIndex(4);
assert_equals(axObj.role, 'AXRole: AXStaticText');
lineText = [];
lineText.push(axObj.name);
// Both the static text object as well as its only inline text box child,
// should be connected to the same inline text box as their "previousOnLine".
assert_equals(axObj.childrenCount, 1);
assert_equals(axObj.childAtIndex(0).role, 'AXRole: AXInlineTextBox');
assert_equals(axObj.childAtIndex(0).previousOnLine(), axObj.previousOnLine());
axObj = axObj.previousOnLine();
assert_equals(axObj.role, 'AXRole: AXInlineTextBox');
lineText.push(axObj.name);
assert_equals(axObj.parentElement().previousOnLine(), undefined);
assert_equals(axObj.previousOnLine(), undefined);
assert_equals(axObj.parentElement().nextOnLine(), axObj.nextOnLine());
axObj = axObj.nextOnLine();
assert_equals(axObj.role, 'AXRole: AXInlineTextBox');
lineText.push(axObj.name);
assert_equals(axObj.parentElement().nextOnLine(), undefined);
assert_equals(axObj.nextOnLine(), undefined);
assert_array_equals(lineText, ['line.', 'second ', 'line.']);
}, 'Test |NextOnLine| and |PreviousOnLine| on multiline inline elements.');
test(() => {
let axEditable = accessibilityController.accessibleElementById('anonymousMultilineInline');
// The tree is:
// [0] AXStaticText "This is a "
// AXInlineTextBox "This is a "
// [1] AXGenericContainer ""
// AXStaticText "test with a"
// AXInlineTextBox "test with a"
// AXLineBreak "\n"
// AXInlineTextBox "\n"
// AXStaticText "second "
// AXInlineTextBox "second "
// [2] AXStaticText "line."
// AXInlineTextBox "line."
// There should be two static text objects and a span.
// The span, represented by a generic container, should be the second child.
assert_equals(axEditable.childrenCount, 3);
axObj = axEditable.childAtIndex(0);
assert_equals(axObj.role, 'AXRole: AXStaticText');
const lineBreak = axEditable.childAtIndex(1).childAtIndex(1);
assert_equals(lineBreak.role, 'AXRole: AXLineBreak');
//
// Test first line.
//
let lineText = [];
lineText.push(axObj.name);
// Both the static text object as well as its only inline text box child,
// should be connected to the same inline text box as their "nextOnLine".
assert_equals(axObj.childrenCount, 1);
assert_equals(axObj.childAtIndex(0).role, 'AXRole: AXInlineTextBox');
assert_equals(axObj.childAtIndex(0).nextOnLine(), axObj.nextOnLine());
axObj = axObj.nextOnLine();
assert_equals(axObj.role, 'AXRole: AXInlineTextBox');
lineText.push(axObj.name);
assert_equals(axObj.parentElement().nextOnLine(), lineBreak.childAtIndex(0),
'"This is a ".axObj.parentElement().nextOnLine()');
assert_equals(axObj.nextOnLine(), lineBreak.childAtIndex(0),
'"This is a ".axObj.nextOnLine()');
// The span should also be connected to the inline text box that starts the first line.
assert_equals(axEditable.childAtIndex(1).role, 'AXRole: AXGenericContainer');
assert_equals(axObj.previousOnLine(),
axEditable.childAtIndex(0).childAtIndex(0),
'axEditable.childAtIndex(1).previousOnLine()');
assert_equals(axObj.parentElement().previousOnLine(), axObj.previousOnLine(),
'"test with a".parentElement().previousOnLine()');
axObj = axObj.previousOnLine();
assert_equals(axObj.role, 'AXRole: AXInlineTextBox');
lineText.push(axObj.name);
assert_equals(axObj.parentElement().previousOnLine(),
undefined,
'"This is a ".parentElement().previousOnLine()');
assert_equals(axObj.previousOnLine(), undefined,
'"This is a ".previousOnLine()');
assert_array_equals(lineText, ['This is a ', 'test with a', 'This is a ']);
//
// Test second line.
//
axObj = axEditable.childAtIndex(2);
assert_equals(axObj.role, 'AXRole: AXStaticText');
lineText = [];
lineText.push(axObj.name);
// Both the static text object as well as its only inline text box child,
// should be connected to the same inline text box as their "previousOnLine".
assert_equals(axObj.childrenCount, 1);
assert_equals(axObj.childAtIndex(0).role, 'AXRole: AXInlineTextBox');
assert_equals(axObj.childAtIndex(0).previousOnLine(), axObj.previousOnLine(),
'"line".childAtIndex(0).previousOnLine()');
axObj = axObj.previousOnLine();
assert_equals(axObj.role, 'AXRole: AXInlineTextBox');
lineText.push(axObj.name);
assert_equals(axObj.parentElement().previousOnLine(), undefined,
'"second ".parentElement().previousOnLine()');
assert_equals(axObj.previousOnLine(), undefined,
'"second ".previousOnLine()');
// The span should also be connected to the inline text box that ends the second line.
assert_equals(axObj.nextOnLine(), axEditable.childAtIndex(2).childAtIndex(0),
'"second ".nextOnLine()');
assert_equals(axObj.parentElement().nextOnLine(),
axEditable.childAtIndex(2).childAtIndex(0),
'"second ".parentElement().nextOnLine()');
axObj = axObj.nextOnLine();
assert_equals(axObj.role, 'AXRole: AXInlineTextBox');
lineText.push(axObj.name);
assert_equals(axObj.parentElement().nextOnLine(), undefined,
'"line.".parentElement().nextOnLine()');
assert_equals(axObj.nextOnLine(), undefined,
'"line.".nextOnLine()');
assert_array_equals(lineText, ['line.', 'second ', 'line.']);
}, 'Test |NextOnLine| and |PreviousOnLine| on anonymous multiline inline elements.');
</script>