summaryrefslogtreecommitdiffstats
path: root/accessible/tests/mochitest/textselection/test_general.html
blob: 92e7988a874f002d81788ab7d563afba2604b680 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
<html>

<head>
  <title>Text selection testing</title>

  <link rel="stylesheet" type="text/css"
        href="chrome://mochikit/content/tests/SimpleTest/test.css" />

  <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>

  <script type="application/javascript"
          src="../common.js"></script>
  <script type="application/javascript"
          src="../promisified-events.js"></script>

  <script type="application/javascript">
    /**
     * Helper function to test selection bounds.
     * @param {string} aID  The ID to test.
     * @param {nsIAccessibleText} acc  The accessible to test.
     * @param {int} index  The selection's index to test.
     * @param {array} offsets  The start and end offset to test against.
     * @param {string} msgStart  The start of the message to return in test
     *                           messages.
     */
    function testSelectionBounds(aID, acc, index, offsets, msgStart) {
      const [expectedStart, expectedEnd] = offsets;
      const startOffset = {}, endOffset = {};
      acc.getSelectionBounds(index, startOffset, endOffset);

      is(startOffset.value, Math.min(expectedStart, expectedEnd),
         msgStart + ": Wrong start offset for " + aID);
      is(endOffset.value, Math.max(expectedStart, expectedEnd),
         msgStart + ": Wrong end offset for " + aID);
    }

    /**
     * Test adding selections to accessibles.
     * @param {string} aID  The ID of the element to test.
     * @param {array} aSelections  Array of selection start and end indices.
     */
    async function addSelections(aID, aSelections) {
      info("Test adding selections to " + aID);
      const hyperText = getAccessible(aID, [ nsIAccessibleText ]);
      const initialSelectionCount = hyperText.selectionCount;

      // Multiple selection changes will be coalesced, so just listen for one.
      const selectionChange = waitForEvent(EVENT_TEXT_SELECTION_CHANGED, aID);
      for (let [startOffset, endOffset] of aSelections) {
        hyperText.addSelection(startOffset, endOffset);
      }
      await selectionChange;

      is(hyperText.selectionCount,
         aSelections.length + initialSelectionCount,
         "addSelection: Wrong selection count for " + aID);

      for (let i in aSelections) {
        testSelectionBounds(aID, hyperText, initialSelectionCount + i,
                            aSelections[i], "addSelection");
      }

      is(hyperText.caretOffset, aSelections[hyperText.selectionCount -1][1],
         "addSelection: caretOffset not at selection end for " + aID);
    }

    /**
     * Test changing selections in accessibles.
     * @param {string} aID  The ID of the element to test.
     * @param {int} aIndex  The index of the selection to change.
     * @param {array} aSelection  Array of the selection's new start and end
     *                            indices.
     */
    async function changeSelection(aID, aIndex, aSelection) {
      info("Test changing the selection of " + aID + " at index " + aIndex);
      const [startOffset, endOffset] = aSelection;
      const hyperText = getAccessible(aID, [ nsIAccessibleText ]);

      const selectionChanged = waitForEvent(EVENT_TEXT_SELECTION_CHANGED, aID);
      hyperText.setSelectionBounds(aIndex, startOffset, endOffset);
      await selectionChanged;

      testSelectionBounds(aID, hyperText, aIndex,
                          aSelection, "setSelectionBounds");

      is(hyperText.caretOffset, endOffset,
         "setSelectionBounds: caretOffset not at selection end for " + aID);
    }

    /**
     * Test removing all selections from accessibles.
     * @param {string} aID  The ID of the element to test.
     */
    async function removeSelections(aID) {
      info("Testing removal of all selections from " + aID);
      const hyperText = getAccessible(aID, [ nsIAccessibleText ]);

      let selectionsRemoved = waitForEvent(EVENT_TEXT_SELECTION_CHANGED, document);
      const selectionCount = hyperText.selectionCount;
      for (let i = 0; i < selectionCount; i++) {
        hyperText.removeSelection(0);
      }
      await selectionsRemoved;

      is(hyperText.selectionCount, 0,
         "removeSelection: Wrong selection count for " + aID);
    }

    /**
     * Test that changing the DOM selection is reflected in the accessibles.
     * @param {string} aID  The container ID to test in
     * @param {string} aNodeID1  The start node of the selection
     * @param  {int} aNodeOffset1  The offset where the selection should start
     * @param {string} aNodeID2  The node in which the selection should end
     * @param {int} aNodeOffset2  The index at which the selection should end
     * @param {array} aTests  An array of accessibles and their start and end
     *                        offsets to test.
     */
    async function changeDOMSelection(aID, aNodeID1, aNodeOffset1,
                                      aNodeID2, aNodeOffset2,
                                      aTests) {
      info("Test that DOM selection changes are reflected in the accessibles");

      let selectionChanged = waitForEvent(EVENT_TEXT_SELECTION_CHANGED, aID);
      // HyperTextAccessible::GetSelectionDOMRanges ignores hidden selections.
      // Here we may be focusing an editable element (and thus hiding the
      // main document selection), so blur it so that we test what we want to
      // test.
      document.activeElement.blur();

      const sel = window.getSelection();
      const range = document.createRange();
      range.setStart(getNode(aNodeID1), aNodeOffset1);
      range.setEnd(getNode(aNodeID2), aNodeOffset2);
      sel.addRange(range);
      await selectionChanged;

      for (let i = 0; i < aTests.length; i++) {
        const text = getAccessible(aTests[i][0], nsIAccessibleText);
        is(text.selectionCount, 1,
           "setSelectionBounds: Wrong selection count for " + aID);
        testSelectionBounds(aID, text, 0, [aTests[i][1], aTests[i][2]],
                            "setSelectionBounds");
      }
    }

    /**
     * Test  expected and unexpected events for selecting
     * all text and focusing both an input and text area. We expect a caret
     * move, but not a text selection change.
     * @param {string} aID  The ID of the element to test.
     */
    async function eventsForSelectingAllTextAndFocus(aID) {
      info("Test expected caretMove and unexpected textSelection events for " +aID);
      let events = waitForEvents({
        expected: [[EVENT_TEXT_CARET_MOVED, aID]],
        unexpected: [[EVENT_TEXT_SELECTION_CHANGED, aID]]}, aID);
      selectAllTextAndFocus(aID);
      await events;
    }

    /**
     * Do tests
     */

    async function doTests() {
      await addSelections("paragraph", [[1, 3], [6, 10]]);
      await changeSelection("paragraph", 0, [2, 4]);
      await removeSelections("paragraph");

      // reverse selection
      await addSelections("paragraph", [[1, 3], [10, 6]]);
      await removeSelections("paragraph");

      await eventsForSelectingAllTextAndFocus("textbox");
      await changeSelection("textbox", 0, [1, 3]);

      // reverse selection
      await changeSelection("textbox", 0, [3, 1]);

      await eventsForSelectingAllTextAndFocus("textarea");
      await changeSelection("textarea", 0, [1, 3]);

      await changeDOMSelection("c1", "c1_span1", 0, "c1_span2", 0,
                               [["c1", 2, 2]]);
      await changeDOMSelection("c2", "c2", 0, "c2_div2", 1,
                               [["c2", 0, 3], ["c2_div2", 0, 2]]);

      SimpleTest.finish();
    }

    SimpleTest.waitForExplicitFinish();
    addA11yLoadEvent(doTests);
  </script>
</head>

<body>

  <a target="_blank"
     href="https://bugzilla.mozilla.org/show_bug.cgi?id=688126"
     title="nsIAccessibleText::setSelectionBounds doesn't fire text selection changed events in some cases">
    Bug 688126
  </a>
  <a target="_blank"
     href="https://bugzilla.mozilla.org/show_bug.cgi?id=688124"
     title="no text selection changed event when selection is removed">
    Bug 688124
  </a>
  <p id="display"></p>
  <div id="content" style="display: none"></div>
  <pre id="test">
  </pre>

  <p id="paragraph">hello world</p>
  <input id="textbox" value="hello"/>
  <textarea id="textarea">hello</textarea>
  <div id="c1">hi<span id="c1_span1"></span><span id="c1_span2"></span>hi</div>
  <div id="c2">hi<div id="c2_div2">hi</div></div>

</body>
</html>