| <!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().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().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> |