chromium/third_party/blink/web_tests/accessibility/contenteditable-caret-position.html

<!DOCTYPE html>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>

<div id="main" role="main">

    <div id="contenteditable-textbox" role="textbox" contenteditable="true">
        <div id="contenteditable-line1">Line 1</div>
        <textarea id="contenteditable-line2" rows="1" cols="40">Line 2</textarea>
    </div>

    <div id="contenteditable-div" contenteditable>
        <p id="paragraph1">Line 1<br>
            Line 2</p>
        <p id="paragraph2">Line 3</p>
    </div>

    <div id="contenteditable-div-2" contenteditable role="textbox"
        style="max-width: 5px; overflow-wrap: normal;">
        Line 1<br>
        Line 2
    </div>

</div>

<script>
    test(function()
    {
        let mainAccessible = accessibilityController.accessibleElementById("main");

        assert_equals(mainAccessible.selectionAnchorObject, null);
        assert_equals(mainAccessible.selectionAnchorOffset, -1);
        assert_equals(mainAccessible.selectionFocusObject, null);
        assert_equals(mainAccessible.selectionFocusOffset, -1);
    }, "Initially there should be no selection under the main object.");

    test(function()
    {
        let rootAccessible = accessibilityController.rootElement;

        assert_equals(rootAccessible.selectionAnchorObject, null);
        assert_equals(rootAccessible.selectionAnchorOffset, -1);
        assert_equals(rootAccessible.selectionFocusObject, null);
        assert_equals(rootAccessible.selectionFocusOffset, -1);
    }, "Initially there should be no selection on the root object.");

    test(function()
    {
        let textbox = document.getElementById("contenteditable-textbox");
        textbox.focus();
        let textboxAccessible = accessibilityController.accessibleElementById("contenteditable-textbox");
        let line1Accessible = accessibilityController.accessibleElementById("contenteditable-line1");
        let line1TextAccessible = line1Accessible.childAtIndex(0);

        assert_equals(textboxAccessible.selectionAnchorObject, line1TextAccessible);
        assert_equals(textboxAccessible.selectionAnchorOffset, 0);
        assert_equals(textboxAccessible.selectionFocusObject, line1TextAccessible);
        assert_equals(textboxAccessible.selectionFocusOffset, 0);
    }, "Moving the focus to an ARIA textbox should place the caret at its beginning.");

    test(function()
    {
        let selection = window.getSelection();
        let selectionRange = document.createRange();
        let textboxAccessible = accessibilityController.accessibleElementById("contenteditable-textbox");
        let mainAccessible = accessibilityController.accessibleElementById("main");
        let rootAccessible = accessibilityController.rootElement;
        let line1 = document.getElementById("contenteditable-line1");
        let line1Accessible = accessibilityController.accessibleElementById("contenteditable-line1");
        let line1TextAccessible = line1Accessible.childAtIndex(0);

        selectionRange.setStart(line1.firstChild, 1);
        selectionRange.setEnd(line1.firstChild, 1);
        selection.removeAllRanges();
        selection.addRange(selectionRange);

        assert_equals(textboxAccessible.selectionAnchorObject, line1TextAccessible);
        assert_equals(textboxAccessible.selectionAnchorOffset, 1);
        assert_equals(textboxAccessible.selectionFocusObject, line1TextAccessible);
        assert_equals(textboxAccessible.selectionFocusOffset, 1);

        assert_equals(mainAccessible.selectionAnchorObject, line1TextAccessible);
        assert_equals(mainAccessible.selectionAnchorOffset, 1);
        assert_equals(mainAccessible.selectionFocusObject, line1TextAccessible);
        assert_equals(mainAccessible.selectionFocusOffset, 1);

        assert_equals(rootAccessible.selectionAnchorObject, line1TextAccessible);
        assert_equals(rootAccessible.selectionAnchorOffset, 1);
        assert_equals(rootAccessible.selectionFocusObject, line1TextAccessible);
        assert_equals(rootAccessible.selectionFocusOffset, 1);
    }, "Setting a new caret position in the ARIA textbox should be reflected in the accessibility APIs.");

    test(function()
    {
        let selection = window.getSelection();
        let textboxAccessible = accessibilityController.accessibleElementById("contenteditable-textbox");
        let mainAccessible = accessibilityController.accessibleElementById("main");
        let rootAccessible = accessibilityController.rootElement;

        selection.removeAllRanges();

        assert_equals(textboxAccessible.selectionAnchorObject, null);
        assert_equals(textboxAccessible.selectionAnchorOffset, -1);
        assert_equals(textboxAccessible.selectionFocusObject, null);
        assert_equals(textboxAccessible.selectionFocusOffset, -1);

        assert_equals(mainAccessible.selectionAnchorObject, null);
        assert_equals(mainAccessible.selectionAnchorOffset, -1);
        assert_equals(mainAccessible.selectionFocusObject, null);
        assert_equals(mainAccessible.selectionFocusOffset, -1);

        assert_equals(rootAccessible.selectionAnchorObject, null);
        assert_equals(rootAccessible.selectionAnchorOffset, -1);
        assert_equals(rootAccessible.selectionFocusObject, null);
        assert_equals(rootAccessible.selectionFocusOffset, -1);
    }, "Removing the selection should remove the caret completely.");

    test(function()
    {
        let selection = window.getSelection();
        let textboxAccessible = accessibilityController.accessibleElementById("contenteditable-textbox");
        let mainAccessible = accessibilityController.accessibleElementById("main");
        let rootAccessible = accessibilityController.rootElement;
        let line1Accessible = accessibilityController.accessibleElementById("contenteditable-line1");
        let line1TextAccessible = line1Accessible.childAtIndex(0);

        line1TextAccessible.setSelectedTextRange(2, 0);

        assert_equals(textboxAccessible.selectionAnchorObject, line1TextAccessible);
        assert_equals(textboxAccessible.selectionAnchorOffset, 2);
        assert_equals(textboxAccessible.selectionFocusObject, line1TextAccessible);
        assert_equals(textboxAccessible.selectionFocusOffset, 2);

        assert_equals(mainAccessible.selectionAnchorObject, line1TextAccessible);
        assert_equals(mainAccessible.selectionAnchorOffset, 2);
        assert_equals(mainAccessible.selectionFocusObject, line1TextAccessible);
        assert_equals(mainAccessible.selectionFocusOffset, 2);

        assert_equals(rootAccessible.selectionAnchorObject, line1TextAccessible);
        assert_equals(rootAccessible.selectionAnchorOffset, 2);
        assert_equals(rootAccessible.selectionFocusObject, line1TextAccessible);
        assert_equals(rootAccessible.selectionFocusOffset, 2);
    }, "Positioning the caret using the accessibility API instead of the DOM should work.");

    test(function()
    {
        let line2Accessible = accessibilityController.accessibleElementById("contenteditable-line2");
        let textboxAccessible = accessibilityController.accessibleElementById("contenteditable-textbox");
        let mainAccessible = accessibilityController.accessibleElementById("main");
        let line2 = document.getElementById("contenteditable-line2");
        line2.focus();

        assert_equals(line2Accessible.selectionAnchorObject, line2Accessible);
        assert_equals(line2Accessible.selectionAnchorOffset, 0);
        assert_equals(line2Accessible.selectionFocusObject, line2Accessible);
        assert_equals(line2Accessible.selectionFocusOffset, 0);

        assert_equals(textboxAccessible.selectionAnchorObject, line2Accessible);
        assert_equals(textboxAccessible.selectionAnchorOffset, 0);
        assert_equals(textboxAccessible.selectionFocusObject, line2Accessible);
        assert_equals(textboxAccessible.selectionFocusOffset, 0);

        assert_equals(mainAccessible.selectionAnchorObject, line2Accessible);
        assert_equals(mainAccessible.selectionAnchorOffset, 0);
        assert_equals(mainAccessible.selectionFocusObject, line2Accessible);
        assert_equals(mainAccessible.selectionFocusOffset, 0);
    }, "Moving the focus into a textarea should remove the caret from the ARIA textbox.");

    test(function()
    {
        document.getElementById("contenteditable-line2").focus();
        let rootAccessible = accessibilityController.rootElement;
        let line2Accessible = accessibilityController.focusedElement;

        assert_equals(rootAccessible.selectionAnchorObject, line2Accessible);
        assert_equals(rootAccessible.selectionAnchorOffset, 0);
        assert_equals(rootAccessible.selectionFocusObject, line2Accessible);
        assert_equals(rootAccessible.selectionFocusOffset, 0);

        assert_equals(line2Accessible.selectionAnchorObject, line2Accessible);
        assert_equals(line2Accessible.selectionAnchorOffset, 0);
        assert_equals(line2Accessible.selectionFocusObject, line2Accessible);
        assert_equals(line2Accessible.selectionFocusOffset, 0);
    }, "Standard text fields start with the caret at the beginning of their contents.");

    test(function()
    {
        let line2 = document.getElementById("contenteditable-line2");
        line2.focus();
        let line2Accessible = accessibilityController.focusedElement;

        line2.setSelectionRange(3, 3);

        assert_equals(line2Accessible.selectionAnchorObject, line2Accessible);
        assert_equals(line2Accessible.selectionAnchorOffset, 3);
        assert_equals(line2Accessible.selectionFocusObject, line2Accessible);
        assert_equals(line2Accessible.selectionFocusOffset, 3);
    }, "Setting a new caret position in the textarea should be exposed in the accessibility APIs.");

    test(function()
    {
        let textboxAccessible = accessibilityController.accessibleElementById("contenteditable-textbox");
        let mainAccessible = accessibilityController.accessibleElementById("main");
        let rootAccessible = accessibilityController.rootElement;
        document.getElementById("contenteditable-line2").focus();
        let line2Accessible = accessibilityController.focusedElement;

        assert_equals(textboxAccessible.selectionAnchorObject, line2Accessible);
        assert_equals(textboxAccessible.selectionAnchorOffset, 3);
        assert_equals(textboxAccessible.selectionFocusObject, line2Accessible);
        assert_equals(textboxAccessible.selectionFocusOffset, 3);

        assert_equals(mainAccessible.selectionAnchorObject, line2Accessible);
        assert_equals(mainAccessible.selectionAnchorOffset, 3);
        assert_equals(mainAccessible.selectionFocusObject, line2Accessible);
        assert_equals(mainAccessible.selectionFocusOffset, 3);

        assert_equals(rootAccessible.selectionAnchorObject, line2Accessible);
        assert_equals(rootAccessible.selectionAnchorOffset, 3);
        assert_equals(rootAccessible.selectionFocusObject, line2Accessible);
        assert_equals(rootAccessible.selectionFocusOffset, 3);
    }, "Offsets in text fields should be reported from the beginning of the field and not from the top of the container.");

    test(function()
    {
        var line1Accessible = accessibilityController.accessibleElementById("contenteditable-line1");
        var line2Accessible = accessibilityController.accessibleElementById("contenteditable-line2");

        assert_equals(line1Accessible.selectionAnchorObject, line2Accessible);
        assert_equals(line1Accessible.selectionAnchorOffset, 3);
        assert_equals(line1Accessible.selectionFocusObject, line2Accessible);
        assert_equals(line1Accessible.selectionFocusOffset, 3);
        assert_equals(line2Accessible.selectionAnchorObject, line2Accessible);
        assert_equals(line2Accessible.selectionAnchorOffset, 3);
        assert_equals(line2Accessible.selectionFocusObject, line2Accessible);
        assert_equals(line2Accessible.selectionFocusOffset, 3);
    }, "The caret position should be retrievable from any object.");

    test(function()
    {
        const selection = window.getSelection();
        const selectionRange = document.createRange();
        const mainAccessible = accessibilityController.accessibleElementById("main");
        const rootAccessible = accessibilityController.rootElement;

        const contenteditable = document.getElementById("contenteditable-div");
        contenteditable.focus();
        // The offset from the newline character between the two lines of the
        // first paragraph to the first character of its second line.
        // (Needed for skipping wide space.)
        const line2StartOffset = 13;

        const line1 = document.getElementById("paragraph1").firstChild;
        const line2 = document.getElementById("paragraph1").lastChild;
        const line3 = document.getElementById("paragraph2").firstChild;
        const contenteditableLines = [ line1, line2, line3 ];
        
        const contenteditableAccessible = accessibilityController.accessibleElementById("contenteditable-div");
        const paragraph1Accessible = accessibilityController.accessibleElementById("paragraph1");
        const paragraph2Accessible = accessibilityController.accessibleElementById("paragraph2");
        const line1Accessible = paragraph1Accessible.childAtIndex(0);
        const line2Accessible = paragraph1Accessible.childAtIndex(2);
        const line3Accessible = paragraph2Accessible.childAtIndex(0);
        const expectations = [
          line1Accessible, line2Accessible, line3Accessible
        ];

        for (let lineNumber = 0; lineNumber < 3; ++lineNumber) {
            for (let characterOffset = 0; characterOffset < 7; ++characterOffset) {
                // Any widespace in the DOM should be stripped out in the
                // accessibility tree.
                let selectionOffset = characterOffset;
                if (lineNumber == 1)
                    selectionOffset += line2StartOffset;

                selectionRange.setStart(contenteditableLines[lineNumber], selectionOffset);
                selectionRange.setEnd(contenteditableLines[lineNumber], selectionOffset);
                selection.removeAllRanges();
                selection.addRange(selectionRange);

                assert_equals(contenteditableAccessible.selectionAnchorObject, expectations[lineNumber]);
                assert_equals(contenteditableAccessible.selectionAnchorOffset, characterOffset);
                assert_equals(contenteditableAccessible.selectionFocusObject, expectations[lineNumber]);
                assert_equals(contenteditableAccessible.selectionFocusOffset, characterOffset);

                assert_equals(mainAccessible.selectionAnchorObject, expectations[lineNumber]);
                assert_equals(mainAccessible.selectionAnchorOffset, characterOffset);
                assert_equals(mainAccessible.selectionFocusObject, expectations[lineNumber]);
                assert_equals(mainAccessible.selectionFocusOffset, characterOffset);

                assert_equals(rootAccessible.selectionAnchorObject, expectations[lineNumber]);
                assert_equals(rootAccessible.selectionAnchorOffset, characterOffset);
                assert_equals(rootAccessible.selectionFocusObject, expectations[lineNumber]);
                assert_equals(rootAccessible.selectionFocusOffset, characterOffset);
            }
        }

    }, "Test moving the caret across two paragraphs by re-creating the selection.");

    test(function()
    {
        const selection = window.getSelection();
        const selectionRange = document.createRange();
        const mainAccessible = accessibilityController.accessibleElementById("main");
        const rootAccessible = accessibilityController.rootElement;

        const contenteditable = document.getElementById("contenteditable-div");
        contenteditable.focus();
        
        const line1 = document.getElementById("paragraph1").firstChild;
        selectionRange.setStart(line1, 0);
        selectionRange.setEnd(line1, 0);
        selection.removeAllRanges();
        selection.addRange(selectionRange);

        const contenteditableAccessible = accessibilityController.accessibleElementById("contenteditable-div");
        const paragraph1Accessible = accessibilityController.accessibleElementById("paragraph1");
        const paragraph2Accessible = accessibilityController.accessibleElementById("paragraph2");
        const line1Accessible = paragraph1Accessible.childAtIndex(0);
        const line2Accessible = paragraph1Accessible.childAtIndex(2);
        const line3Accessible = paragraph2Accessible.childAtIndex(0);
        const expectations = [
          line1Accessible, line2Accessible, line3Accessible
        ];

        for (let lineNumber = 0; lineNumber < 3; ++lineNumber) {
            for (let characterOffset = 0; characterOffset < 7; ++characterOffset) {
                assert_equals(contenteditableAccessible.selectionAnchorObject, expectations[lineNumber]);
                assert_equals(contenteditableAccessible.selectionAnchorOffset, characterOffset);
                assert_equals(contenteditableAccessible.selectionFocusObject, expectations[lineNumber]);
                assert_equals(contenteditableAccessible.selectionFocusOffset, characterOffset);

                assert_equals(mainAccessible.selectionAnchorObject, expectations[lineNumber]);
                assert_equals(mainAccessible.selectionAnchorOffset, characterOffset);
                assert_equals(mainAccessible.selectionFocusObject, expectations[lineNumber]);
                assert_equals(mainAccessible.selectionFocusOffset, characterOffset);

                assert_equals(rootAccessible.selectionAnchorObject, expectations[lineNumber]);
                assert_equals(rootAccessible.selectionAnchorOffset, characterOffset);
                assert_equals(rootAccessible.selectionFocusObject, expectations[lineNumber]);
                assert_equals(rootAccessible.selectionFocusOffset, characterOffset);

                selection.modify('move', 'forward', 'character');
            }
        }
        
    }, "Test moving the caret across two paragraphs by modifying the existing selection.");

    test(function()
    {
        const selection = window.getSelection();
        const selectionRange = document.createRange();
        const mainAccessible = accessibilityController.accessibleElementById("main");
        const rootAccessible = accessibilityController.rootElement;

        const contenteditable = document.getElementById('contenteditable-div-2');
        contenteditable.focus();
        selectionRange.setStart(contenteditable, 0);
        selectionRange.setEnd(contenteditable, 0);
        selection.removeAllRanges();
        selection.addRange(selectionRange);

        const contenteditableAccessible = accessibilityController.accessibleElementById('contenteditable-div-2');
        const line1Accessible = contenteditableAccessible.childAtIndex(0);
        const line2Accessible = contenteditableAccessible.childAtIndex(2);
        const expectations = [ line1Accessible, line2Accessible ];

        for (let lineNumber = 0; lineNumber < 2; ++lineNumber) {
            for (let characterOffset = 0; characterOffset < 7; ++characterOffset) {
                assert_equals(contenteditableAccessible.selectionAnchorObject, expectations[lineNumber]);
                assert_equals(contenteditableAccessible.selectionAnchorOffset, characterOffset);
                assert_equals(contenteditableAccessible.selectionFocusObject, expectations[lineNumber]);
                assert_equals(contenteditableAccessible.selectionFocusOffset, characterOffset);

                assert_equals(mainAccessible.selectionAnchorObject, expectations[lineNumber]);
                assert_equals(mainAccessible.selectionAnchorOffset, characterOffset);
                assert_equals(mainAccessible.selectionFocusObject, expectations[lineNumber]);
                assert_equals(mainAccessible.selectionFocusOffset, characterOffset);

                assert_equals(rootAccessible.selectionAnchorObject, expectations[lineNumber]);
                assert_equals(rootAccessible.selectionAnchorOffset, characterOffset);
                assert_equals(rootAccessible.selectionFocusObject, expectations[lineNumber]);
                assert_equals(rootAccessible.selectionFocusOffset, characterOffset);

                selection.modify('move', 'forward', 'character');
            }
        }
        
    }, "Test moving the caret across two lines that wrap by modifying the existing selection.");
</script>