2011-04-22 Xiaomei Ji <xji@chromium.org>
[WebKit-https.git] / LayoutTests / editing / selection / move-by-word-visually.html
1 <!DOCTYPE HTML>
2 <html>
3 <head>
4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
5 <style>
6 div.test {
7     -webkit-user-modify: read-write;
8     padding: 4px;
9     border: 1px dashed lightblue;
10     margin: 4px 4px 4px 24px;
11     outline: none;
12     font-family: Lucida Grande;
13     counter-increment: test-number;
14 }
15 div.test:before { content: counter(test-number); position: absolute; left: 8px; font-size: x-small; text-align: right; width: 20px; }
16 div.test span { background-color: #def; }
17 div.test img { width: 1em; height: 1em; background-color: lightgreen; }
18 div.test img + img { background-color: lightblue; }
19 div.test div { border: 1px dashed pink; padding: 3px; height: 2em; }
20 test_move_by_word {display: none;}
21 </style>
22 <script>
23 var messages = [];
24
25 function log(message)
26 {
27     messages.push(message);
28 }
29
30 function flushLog()
31 {
32     document.getElementById("console").appendChild(document.createTextNode(messages.join("")));
33 }
34
35 function fold(string)
36 {
37     var result = "";
38     for (var i = 0; i < string.length; ++i) {
39         var char = string.charCodeAt(i);
40         if (char >= 0x05d0)
41             char -= 0x058f;
42         else if (char == 10) {
43             result += "\\n";
44             continue;
45         }
46         result += String.fromCharCode(char);
47     }
48     return result;
49 }
50
51 function logPositions(positions)
52 {
53     for (var i = 0; i < positions.length; ++i) {
54         if (i) {
55             if (positions[i].node != positions[i - 1].node)
56                 log("]");
57             log(", ");
58         }
59         if (!i || positions[i].node != positions[i - 1].node)
60             log((positions[i].node instanceof Text ? '"' + fold(positions[i].node.data) + '"' : "<" + positions[i].node.tagName + ">") + "[");
61         log(positions[i].offset);
62     }
63     log("]");
64 }
65
66 function nodeOfWordBreak(nodeAndOffset)
67 {
68     var node = document.getElementById(nodeAndOffset[0]).firstChild;
69     if (nodeAndOffset.length == 3) {
70         var childIndex = nodeAndOffset[2];
71         for (var i = 0; i < childIndex - 1; ++i) {
72             node = node.nextSibling;
73         }
74     }
75     return node;
76 }
77
78 var wordBreaks;
79
80 function logWordBreak(index, first)
81 {
82     var withNodeData = false;
83     var wordBreak = wordBreaks[index];
84     if (wordBreak.search(',') == -1)
85         log(wordBreak);
86     else {
87         var nodeAndOffset = wordBreak.split(',');
88         var node = nodeOfWordBreak(nodeAndOffset);
89
90         var differentNode = false;
91         if (first == false) {
92             differentNode = nodeOfWordBreak(nodeAndOffset) != nodeOfWordBreak(wordBreaks[index - 1].split(','));
93         
94         }
95
96         if (differentNode == true)
97             log("]");
98
99         if (first == true || differentNode == true) {
100             withNodeData = (node instanceof Text);
101             log((node instanceof Text ? '"' + fold(node.data) + '"' : "<" + node.tagName + ">") + "[");
102         }
103         log(nodeAndOffset[1]);
104     }
105     return withNodeData;
106 }
107
108 function positionEqualToWordBreak(position, wordBreak)
109 {
110     if (wordBreak.search(',') == -1)
111         return position.offset == wordBreak;
112     else {
113         var nodeAndOffset = wordBreak.split(',');
114         return position.node == nodeOfWordBreak(nodeAndOffset) && position.offset == nodeAndOffset[1];
115     }
116 }
117
118 function validateData(positions)
119 {
120     var equal = true;
121     if (positions.length != wordBreaks.length)
122         equal = false;
123     for (var i = 0; i < wordBreaks.length - 1; ++i) {
124         if (!positionEqualToWordBreak(positions[i], wordBreaks[i])) {
125             equal = false;
126             break;
127         }
128     }
129     if (equal == false) {
130         log("    FAIL expected: [");
131         for (var i = 0; i < wordBreaks.length; ++i) {
132             logWordBreak(i, i == 0);
133             if (i != wordBreaks.length - 1)
134                 log(", ");
135         }
136         log("]");
137     }
138 }
139
140 function collectWordBreaks(test, searchDirection)
141 {
142     var title;
143     if (searchDirection == "right") 
144         title = test.title.split("|")[0];
145     else
146         title = test.title.split("|")[1];
147
148     var pattern = /\[(.+?)\]/g;
149     var result;
150     wordBreaks = [];
151     while ((result = pattern.exec(title)) != null) {
152         wordBreaks.push(result[1]);
153     }
154     if (wordBreaks.length == 0) {
155         wordBreaks = title.split(" ");
156     }
157 }
158
159 function setPosition(sel, node, wordBreak)
160 {
161     if (wordBreak.search(',') == -1)
162         sel.setPosition(node, wordBreak);
163     else {
164         var nodeAndOffset = wordBreak.split(',');
165         sel.setPosition(nodeOfWordBreak(nodeAndOffset), nodeAndOffset[1]);
166     }
167 }
168
169 function moveByWord(sel, test, searchDirection, dir)
170 {
171     log("Move " + searchDirection + " by one word\n");
172     var prevOffset = sel.anchorOffset;
173     var prevNode = sel.anchorNode;
174     var positions = [];
175     positions.push({ node: sel.anchorNode, offset: sel.anchorOffset });
176
177     while (1) {
178         sel.modify("move", searchDirection, "-webkit-visual-word");
179         if (prevNode == sel.anchorNode && prevOffset == sel.anchorOffset)
180             break;
181         positions.push({ node: sel.anchorNode, offset: sel.anchorOffset });
182         prevNode = sel.anchorNode;
183         prevOffset = sel.anchorOffset;
184     };
185     logPositions(positions);
186     collectWordBreaks(test, searchDirection);
187     validateData(positions);
188     log("\n");
189 }
190
191 function positionPassWordBreak(position, wordBreak, searchDirection)
192 {
193     // Hack for space collapse.
194     // sel.modify("move", dir, "character") not reach multi-space.
195     var node = position.node;
196     if (node.parentNode.id == "multispace") {
197         if (searchDirection == "right" && position.offset >= wordBreak
198             || searchDirection == "left" && position.offset <= wordBreak) 
199             return true;
200     }
201     return false;
202 }
203
204 function moveByWordOnEveryChar(sel, test, searchDirection, dir)
205 {
206     collectWordBreaks(test, searchDirection);
207     var wordBreakIndex = 1;
208     var prevOffset = sel.anchorOffset;
209     var prevNode = sel.anchorNode;
210
211     while (1) {
212         var positions = [];
213         positions.push({ node: sel.anchorNode, offset: sel.anchorOffset });
214         sel.modify("move", searchDirection, "-webkit-visual-word");
215
216         var position = { node: sel.anchorNode, offset: sel.anchorOffset };
217
218         if (wordBreakIndex >= wordBreaks.length) {
219             if (sel.anchorNode != prevNode || sel.anchorOffset != prevOffset) {
220                 positions.push(position);
221                 logPositions(positions);
222                 log("   FAIL expected to stay in the same position\n");
223             }
224         } else if (!positionEqualToWordBreak(position, wordBreaks[wordBreakIndex])) {
225             positions.push(position);
226             logPositions(positions);
227             log("   FAIL expected ");
228             var withNodeData = logWordBreak(wordBreakIndex, true);
229             if (withNodeData)
230                 log("]");
231             log("\n");
232         }
233
234         // Restore position and move by 1 character.
235         sel.setPosition(prevNode, prevOffset);
236         sel.modify("move", searchDirection, "character");
237         if (prevNode == sel.anchorNode && prevOffset == sel.anchorOffset)
238             break;
239
240         position = { node: sel.anchorNode, offset: sel.anchorOffset };
241         if (wordBreakIndex < wordBreaks.length 
242             && (positionEqualToWordBreak(position, wordBreaks[wordBreakIndex]) 
243             || positionPassWordBreak(position, wordBreaks[wordBreakIndex], searchDirection)))
244             ++wordBreakIndex;
245
246         prevNode = sel.anchorNode;
247         prevOffset = sel.anchorOffset;
248     };
249 }
250
251 function moveByWordForEveryPosition(sel, test, dir)
252 {
253     // Check ctrl-right-arrow works for every position.
254     sel.setPosition(test, 0);
255     var direction = "right";
256     if (dir == "rtl")
257         direction = "left";    
258     moveByWord(sel, test, direction, dir);    
259     sel.setPosition(test, 0);
260     moveByWordOnEveryChar(sel, test, direction, dir);
261
262     sel.modify("move", "forward", "lineBoundary");
263     // Check ctrl-left-arrow works for every position.
264     if (dir == "ltr")
265         direction = "left";
266     else
267         direction = "right";    
268     moveByWord(sel, test, direction, dir);    
269     sel.modify("move", "forward", "lineBoundary");
270     moveByWordOnEveryChar(sel, test, direction, dir);
271 }
272
273 function runMoveLeftRight(tests, unit)
274 {
275     var sel = getSelection();
276     for (var i = 0; i < tests.length; ++i) {
277         var positionsMovingRight;
278         var positionsMovingLeft;
279
280         if (tests[i].getAttribute("dir") == 'ltr')
281         {
282             log("Test " + (i + 1) + ", LTR:\n");
283             moveByWordForEveryPosition(sel, tests[i], "ltr");
284         } else {
285             log("Test " + (i + 1) + ", RTL:\n");
286             moveByWordForEveryPosition(sel, tests[i], "rtl");
287         }
288     }
289
290     document.getElementById("testMoveByWord").style.display = "none";
291 }
292
293 function runTest() {
294     log("\n======== Move By Word ====\n");
295     var tests = document.getElementsByClassName("test_move_by_word");
296     runMoveLeftRight(tests, "word");
297 }
298
299 onload = function() {
300     try {
301         runTest();
302     } finally {
303         flushLog();
304     }
305 };
306
307 if (window.layoutTestController)
308     layoutTestController.dumpAsText();
309 </script>
310 </head>
311 <body>
312 <div id="testMoveByWord">
313 <!-- 
314 Title saves the word breaks.
315 The format of title is "xxx|xxxx".
316
317 The sequence on the left of "|" is word boundaries when moving caret from left to right.
318 The sequence on the right of "|" is word boundaries when moving caret from right to left.
319
320 If there is a single node in the line, the sequence are offsets.
321 If there are multiple nodes in the line, the sequence is array of [anchor_node_id, offset, child_node_index], 
322 where child_node_index is optional, default is the first child of the anchor node.
323 -->
324 <div dir=ltr class="test_move_by_word" title="0 4 8 12 16|19 16 12 8 4 0" contenteditable>abc def hij opq rst</div>
325
326 <!-- pure English -->
327 <div dir=ltr class="test_move_by_word" title="0 4 8 12 16|19 16 12 8 4 0" contenteditable>abc def hij opq rst</div>
328 <div dir=rtl class="test_move_by_word" title="19 3 7 11 15 0|0 15 11 7 3" contenteditable>abc def hij opq rst</div>
329
330 <!-- pure Hebrew -->
331 <div dir=ltr class="test_move_by_word" title="0 15 11 7 3|19 3 7 11 15 0" contenteditable>ששש נננ בבב גגג קקק</div>
332 <div dir=rtl class="test_move_by_word" title="19 16 12 8 4 0|0 4 8 12 16" contenteditable>ששש נננ בבב גגג קקק</div>
333
334 <!-- bidi text -->
335 <!-- English Hebrew English -->
336 <div dir=ltr class="test_move_by_word" title="0 4 8 12 19 15 24 28 32|35 32 28 24 15 19 12 8 4 0" contenteditable>abc def hij אאא בבב צצצ opr uvw xyz</div>
337 <div dir=rtl class="test_move_by_word" title="35 27 31 24 20 16 12 3 7 0|0 7 3 12 16 20 24 31 27" contenteditable>abc def hij אאא בבב צצצ opr uvw xyz</div>
338
339 <div dir=ltr class="test_move_by_word" title="0 4 8 11 16 20|23 20 16 11 8 4 0" contenteditable>abc def שנב סטז uvw xyz</div>
340 <div dir=rtl class="test_move_by_word" title="23 19 16 12 8 3 0|0 3 8 12 16 19" contenteditable>abc def שנב סטז uvw xyz</div>
341
342 <div dir=ltr class="test_move_by_word" title="0 4 8|11 8 4 0" contenteditable>aaa אאא bbb</div>
343 <div dir=rtl class="test_move_by_word" title="11 8 4 0|0 4 8 11" contenteditable>aaa אאא bbb</div>
344
345 <!-- Hebrew English Hebrew -->
346 <div dir=ltr class="test_move_by_word" title="0 7 3 12 16 20 24 31 27|35 27 31 24 20 16 12 3 7 0" contenteditable>אאא בבב צצצ aaa bbb ccc דדד עעע פפפ</div>
347 <div dir=rtl class="test_move_by_word" title="35 32 28 24 15 19 12 8 4 0|0 4 8 12 19 15 24 28 32" contenteditable>אאא בבב צצצ aaa bbb ccc דדד עעע פפפ</div>
348
349 <div dir=ltr class="test_move_by_word" title="0 3 8 12 16 19|23 19 16 12 8 3 0" contenteditable>אאא בבב aaa bbb צצצ דדד</div>
350 <div dir=rtl class="test_move_by_word" title="23 20 16 11 8 4 0|0 4 8 11 16 20" contenteditable>אאא בבב aaa bbb צצצ דדד</div>
351
352 <div dir=ltr class="test_move_by_word" title="0 4 8 11|11 8 4 0" contenteditable>שנב abc סטז</div>
353 <div dir=rtl class="test_move_by_word" title="11 8 4 0|0 4 8" contenteditable>שנב abc סטז</div>
354
355 <!-- multiple spaces between word. 
356      FAILED: word break between "def" and "hij" is unreachable.
357 -->
358 <div id="multispace" dir=ltr class="test_move_by_word" title="0 4 11 15|18 15 11 4 0" contenteditable>abc def    hij opq</div>
359
360 <!-- Inline element -->
361 <div dir=ltr id="div_1" class="test_move_by_word" title="[div_1, 0][div_1, 3]|[span_1, 2][div_1, 3][div_1,0]" contenteditable>אאא <span id="span_1">בב</span></div>
362 <div dir=rtl id="div_2" class="test_move_by_word" title="[span_2, 2][div_2, 4][div_2, 0]|[div_2, 0][div_2, 4]" contenteditable>אאא <span id="span_2">בב</span></div>
363
364 <!-- pure English in inline element with same or different directionality from its parent -->
365 <div dir=ltr id="div_3" class="test_move_by_word" title="[div_3, 0][div_3, 4][div_3, 8][span_3, 4][div_3, 1, 3][div_3, 5, 3]|[div_3, 8, 3][div_3, 5, 3][div_3, 1, 3][span_3, 4][div_3, 8][div_3, 4][div_3, 0]" contenteditable>abc def <span id="span_3">hij opq</span> rst uvw</div>
366
367 <!-- FAILED -->
368 <div dir=rtl id="div_4" class="test_move_by_word" title="[div_4, 8, 3][div_4, 3, 1][div_4, 7, 1][span_4, 3, 1][span_4, 7, 1][div_4, 4, 3][div_4, 0, 1]|[div_4, 0, 1][div_4, 4, 3][span_4, 7, 1][span_4, 3, 1][div_4, 7, 1][div_4, 3, 1]" contenteditable>abc def <span id="span_4">hij opq</span> rst uvw</div>
369
370 <!-- FAILED. The render result is the same as div_4. -->
371 <div id="div_5" dir=rtl class="test_move_by_word" title="[div_5, 8, 3][div_5, 3, 1][div_5, 7, 1][span_5, 3, 1][span_5, 7, 1][div_5, 4, 3][div_5, 0, 1]|[div_5, 0, 1][div_5, 4, 3][span_5, 7, 1][span_5, 3, 1][div_5, 7, 1][div_5, 3, 1]"contenteditable>abc def <span dir=ltr id="span_5">hij opq</span> rst uvw</div>
372
373 <div id="div_6" dir=ltr class="test_move_by_word" title="[div_6, 0, 1][div_6, 4, 1][div_6, 8, 1][span_6, 4, 1][div_6, 1, 3][div_6, 5, 3]|[div_6, 8, 3][div_6, 5, 3][div_6, 1, 3][span_6, 4, 1][div_6, 8, 1][div_6, 4, 1][div_6, 0, 1]" contenteditable>abc def <span dir=rtl id="span_6">hij opq</span> rst uvw</div>
374
375 <!-- pure Hebrew in inline element with same or different directionality from its parent -->
376
377 <div id="div_7" dir=rtl class="test_move_by_word" title="[div_7, 7, 3][div_7, 4, 3][span_7, 4, 1][div_7, 8, 1][div_7, 4, 1][div_7, 0, 1]|[div_7, 0, 1][div_7, 4, 1][div_7, 8, 1][span_7, 4, 1][div_7, 4, 3]" contenteditable>אבד דעפ <span dir=ltr id="span_7">היח ופק</span>ווש כטז</div>
378
379 <div id="div_8" dir=ltr class="test_move_by_word" title="[div_8, 0, 1][div_8, 3, 3][span_8, 3, 1][div_8, 7, 1][div_8, 3, 1]|[div_8, 7, 3][div_8, 3, 1][div_8, 7, 1][span_8, 3, 1][div_8, 3, 3][div_8, 0, 1]" contenteditable>אבד דעפ <span dir=rtl id="span_8">היח ופק</span>ווש כטז</div>
380
381 <div id="div_9" dir=rtl class="test_move_by_word" title="[div_9, 7, 3][div_9, 4, 3][span_9, 4, 1][div_9, 8, 1][div_9, 4, 1][div_9, 0, 1]|[div_9, 0, 1][div_9, 4, 1][div_9, 8, 1][span_9, 4, 1][div_9, 4, 3]" contenteditable>אבד דעפ <span id="span_9">היח ופק</span>ווש כטז</div>
382
383 <div id="div_10" dir=ltr class="test_move_by_word" title="[div_10, 0, 1][div_10, 3, 3][span_10, 3, 1][div_10, 7, 1][div_10, 3, 1]|[div_10, 7, 3][div_10, 3, 1][div_10, 7, 1][span_10, 3, 1][div_10, 3, 3][div_10, 0, 1]" contenteditable>אבד דעפ <span id="span_10">היח ופק</span>ווש כטז</div>
384
385 <!-- bidi in inline element with same or different directionality from its parent -->
386 <div id="div_11" dir=rtl class="test_move_by_word" title="[div_11, 7, 3][div_11, 4, 3][span_11, 3, 1][div_11, 8, 1][div_11, 4, 1][div_11, 0, 1]|[div_11, 0, 1][div_11, 4, 1][div_11, 8, 1][span_11, 3, 1][div_11, 4, 3]" contenteditable>אבד דעפ <span dir=ltr id="span_11">abc def</span>ווש כטז</div>
387
388 <!-- FAIL -->
389 <div id="div_12" dir=ltr class="test_move_by_word" title="[div_12, 0, 1][div_12, 3, 3][div_12, 8, 1][span_12, 4, 1][div_12, 7, 1][div_12, 3, 1]|[div_12, 7, 3][div_12, 3, 1][div_12, 7, 1][span_12, 4, 1][div_12, 8, 1][div_12, 3, 3][div_12, 0]" contenteditable>אבד דעפ <span dir=rtl id="span_12">abc def</span>ווש כטז</div>
390
391 <div id="div_13" dir=rtl class="test_move_by_word" title="[div_13, 7, 3][div_13, 4, 3][span_13, 3, 1][div_13, 8, 1][div_13, 4, 1][div_13, 0, 1]|[div_13, 0, 1][div_13, 4, 1][div_13, 8, 1][span_13, 3, 1][div_13, 4, 3]" contenteditable>אבד דעפ <span id="span_13">abc def</span>ווש כטז</div>
392
393 <div id="div_14" dir=ltr class="test_move_by_word" title="[div_14, 0, 1][div_14, 3, 1][div_14, 8, 1][span_14, 4, 1][div_14, 3, 3]|[div_14, 7, 3][div_14, 3, 3][span_14, 4, 1][div_14, 8, 1][div_14, 3, 1][div_14, 0, 1]" contenteditable>אבד דעפ <span id="span_14">abc def</span>ווש כטז</div>
394
395 <!-- FAILED -->
396 <div id="div_15" dir=rtl class="test_move_by_word" title="[div_15, 11, 3][div_15, 8, 3][div_15, 4, 3][span_15, 3, 1][span_15, 4, 1][div_15, 12, 1][div_15, 8, 1][div_15, 4, 1][div_15, 0, 1]|[div_15, 0, 1][div_15, 4, 1][div_15, 8, 1][div_15, 12, 1][span_15, 4, 1][span_15, 3, 1][div_15, 4, 3][div_15, 8, 3]" contenteditable>אבד opq דעפ <span dir=ltr id="span_15">abc אאא def</span>ווש rst כטז</div>
397
398 <!-- FAILED, and wrong printing result -->
399 <div id="div_16" dir=ltr class="test_move_by_word" title="[div_16, 0, 1][div_16, 4, 1][div_16, 8, 1][span_16, 8, 1][span_16, 7, 1][div_16, 12, 1][div_16, 11, 1][div_16, 4, 3][div_16, 8, 3][div_16, 11, 3]|[div_16, 11, 3][div_16, 8, 3][div_16, 4, 3][div_16, 11, 1][div_16, 12, 1][span_16, 7, 1][span_16, 8, 1][div_16, 8, 1][div_16, 4, 1][div_16, 0, 1]" contenteditable>אבד opq דעפ <span dir=rtl id="span_16">abc אאא def</span>ווש rst כטז</div>
400
401 <!-- FAILED -->
402 <div id="div_17" dir=rtl class="test_move_by_word" title="[div_17, 11, 3][div_17, 8, 3][div_17, 4, 3][span_17, 8, 1][span_17, 4, 1][div_17, 12, 1][div_17, 8, 1][div_17, 4, 1][div_17, 0, 1]|[div_17, 0, 1][div_17, 4, 1][div_17, 8, 1][div_17, 12, 1][span_17, 4, 1][span_17, 8, 1][div_17, 4, 3][div_17, 8, 3]" contenteditable>אבד opq דעפ <span id="span_17">abc אאא def</span>ווש rst כטז</div>
403
404 <div id="div_18" dir=ltr class="test_move_by_word" title="[div_18, 0, 1][div_18, 4, 1][div_18, 8, 1][div_18, 12, 1][span_18, 4, 1][span_18, 8, 1][div_18, 4, 3][div_18, 8, 3][div_18, 11, 3]|[div_18, 11, 3][div_18, 8, 3][div_18, 4, 3][span_18, 8, 1][span_18, 4, 1][div_18, 12, 1][div_18, 8, 1][div_18, 4, 1][div_18, 0, 1]" contenteditable>אבד opq דעפ <span id="span_18">abc אאא def</span>ווש rst כטז</div>
405
406 <div id="div_19" dir=ltr class="test_move_by_word" title="[div_19, 0, 1][div_19, 4, 1][span_19, 4, 1][span_19, 7, 1]|[div_19, 3, 3][span_19, 7, 1][span_19, 4, 1][div_19, 4, 1][div_19, 0, 1]" contenteditable>aaa <span id="span_19">bbb אאא </span>ווש</div>
407
408 </div>
409
410 <pre id="console"></pre>
411 </body>
412 </html>