| <head> | |
| <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> | |
| <style> | |
| div.test { | |
| -webkit-user-modify: read-write; | |
| padding: 4px; | |
| border: 1px dashed lightblue; | |
| margin: 4px 4px 4px 24px; | |
| outline: none; | |
| font-family: Lucida Grande; | |
| counter-increment: test-number; | |
| } | |
| div.test:before { content: counter(test-number); position: absolute; left: 8px; font-size: x-small; text-align: right; width: 20px; } | |
| div.test span { background-color: #def; } | |
| div.test img { width: 1em; height: 1em; background-color: lightgreen; } | |
| div.test img + img { background-color: lightblue; } | |
| div.test div { border: 1px dashed pink; padding: 3px; height: 2em; } | |
| </style> | |
| <script> | |
| var messages = []; | |
| function log(message) | |
| { | |
| messages.push(message); | |
| } | |
| function flushLog() | |
| { | |
| document.getElementById("console").appendChild(document.createTextNode(messages.join(""))); | |
| } | |
| function caretCoordinates() | |
| { | |
| if (!window.textInputController) | |
| return { x: 0, y :0 }; | |
| var caretRect = textInputController.firstRectForCharacterRange(textInputController.selectedRange()[0], 0); | |
| return { x: caretRect[0], y: caretRect[1] }; | |
| } | |
| function positionsMovingInDirection(sel, direction) | |
| { | |
| var positions = []; | |
| while (true) { | |
| positions.push({ node: sel.anchorNode, offset: sel.anchorOffset, point: caretCoordinates() }); | |
| sel.modify("move", direction, "character"); | |
| if (positions[positions.length - 1].node == sel.anchorNode && positions[positions.length - 1].offset == sel.anchorOffset) | |
| break; | |
| }; | |
| return positions; | |
| } | |
| function fold(string) | |
| { | |
| var result = ""; | |
| for (var i = 0; i < string.length; ++i) { | |
| var char = string.charCodeAt(i); | |
| if (char >= 0x0660) // Arabic numeral 0 | |
| char = char - 0x660 + '0'.charCodeAt(0); | |
| else if (char >= 0x0627) // Alif | |
| char = char - 0x0627 + 'A'.charCodeAt(0); | |
| else if (char >= 0x05d0) | |
| char -= 0x058f; | |
| else if (char == 10) { | |
| result += "\\n"; | |
| continue; | |
| } | |
| result += String.fromCharCode(char); | |
| } | |
| return result; | |
| } | |
| function logPositions(positions) | |
| { | |
| for (var i = 0; i < positions.length; ++i) { | |
| if (i) { | |
| if (positions[i].node != positions[i - 1].node) | |
| log("]"); | |
| log(", "); | |
| } | |
| if (!i || positions[i].node != positions[i - 1].node) | |
| log((positions[i].node instanceof Text ? '"' + fold(positions[i].node.data) + '"' : "<" + positions[i].node.tagName + ">") + "["); | |
| log(positions[i].offset); | |
| } | |
| log("]"); | |
| } | |
| function checkReverseOrder(positions, reversePositions) | |
| { | |
| var mismatch = positions.length != reversePositions.length; | |
| while (!mismatch) { | |
| var pos = positions.pop(); | |
| if (!pos) | |
| break; | |
| var reversePos = reversePositions.shift(); | |
| if (pos.node != reversePos.node || pos.offset != reversePos.offset) | |
| mismatch = true; | |
| } | |
| if (mismatch) | |
| log("WARNING: Moving to the left did not visit the same positions in reverse order as moving to the right.\n"); | |
| } | |
| function checkCoordinatesMovingRightDown(positions) | |
| { | |
| for (var i = 1; i < positions.length; ++i) { | |
| if (positions[i].point.y > positions[i - 1].point.y || positions[i].point.x < positions[i - 1].point.x && positions[i].point.y >= positions[i - 1].point.y) | |
| log("WARNING: Moved in the wrong direction in step " + i + ": from (" + positions[i - 1].point.x + ", " + positions[i - 1].point.y + ") to (" + positions[i].point.x + ", " + positions[i].point.y + ").\n"); | |
| } | |
| } | |
| function checkCoordinatesMovingLeftDown(positions) | |
| { | |
| for (var i = 1; i < positions.length; ++i) { | |
| if (positions[i].point.y > positions[i - 1].point.y || positions[i].point.x > positions[i - 1].point.x && positions[i].point.y >= positions[i - 1].point.y) | |
| log("WARNING: Moved in the wrong direction in step " + i + ": from (" + positions[i - 1].point.x + ", " + positions[i - 1].point.y + ") to (" + positions[i].point.x + ", " + positions[i].point.y + ").\n"); | |
| } | |
| } | |
| function checkCoordinatesMovingRightUp(positions) | |
| { | |
| for (var i = 1; i < positions.length; ++i) { | |
| if (positions[i].point.y < positions[i - 1].point.y || positions[i].point.x < positions[i - 1].point.x && positions[i].point.y <= positions[i - 1].point.y) | |
| log("WARNING: Moved in the wrong direction in step " + i + ": from (" + positions[i - 1].point.x + ", " + positions[i - 1].point.y + ") to (" + positions[i].point.x + ", " + positions[i].point.y + ").\n"); | |
| } | |
| } | |
| function checkCoordinatesMovingLeftUp(positions) | |
| { | |
| for (var i = 1; i < positions.length; ++i) { | |
| if (positions[i].point.y < positions[i - 1].point.y || positions[i].point.x > positions[i - 1].point.x && positions[i].point.y <= positions[i - 1].point.y) | |
| log("WARNING: Moved in the wrong direction in step " + i + ": from (" + positions[i - 1].point.x + ", " + positions[i - 1].point.y + ") to (" + positions[i].point.x + ", " + positions[i].point.y + ").\n"); | |
| } | |
| } | |
| function runTest() | |
| { | |
| var tests = document.getElementsByClassName("test"); | |
| var sel = getSelection(); | |
| for (var i = 0; i < tests.length; ++i) { | |
| var positionsMovingRight; | |
| var positionsMovingLeft; | |
| log("Test " + (i + 1) + ", LTR:\n Moving right: "); | |
| sel.setPosition(tests[i], 0); | |
| positionsMovingRight = positionsMovingInDirection(sel, "right"); | |
| logPositions(positionsMovingRight); | |
| log("\n"); | |
| checkCoordinatesMovingRightDown(positionsMovingRight); | |
| log(" Moving left: "); | |
| positionsMovingLeft = positionsMovingInDirection(sel, "left"); | |
| logPositions(positionsMovingLeft); | |
| log("\n"); | |
| checkCoordinatesMovingLeftUp(positionsMovingLeft); | |
| checkReverseOrder(positionsMovingLeft, positionsMovingRight); | |
| tests[i].style.direction = "rtl"; | |
| log("Test " + (i + 1) + ", RTL:\n Moving left: "); | |
| sel.setPosition(tests[i], 0); | |
| positionsMovingLeft = positionsMovingInDirection(sel, "left"); | |
| logPositions(positionsMovingLeft); | |
| log("\n"); | |
| checkCoordinatesMovingLeftDown(positionsMovingLeft); | |
| log(" Moving right: "); | |
| positionsMovingRight = positionsMovingInDirection(sel, "right"); | |
| logPositions(positionsMovingRight); | |
| log("\n"); | |
| checkCoordinatesMovingRightUp(positionsMovingRight); | |
| checkReverseOrder(positionsMovingLeft, positionsMovingRight); | |
| } | |
| document.getElementById("testGroup").style.display = "none"; | |
| } | |
| onload = function() { | |
| try { | |
| runTest(); | |
| } finally { | |
| flushLog(); | |
| } | |
| }; | |
| if (window.testRunner) | |
| testRunner.dumpAsText(); | |
| </script> | |
| </head> | |
| <body> | |
| <div id="testGroup"> | |
| <div class="test"> | |
| abc | |
| </div> | |
| <div class="test"> | |
| אבג | |
| </div> | |
| <div class="test"><br>abc | |
| </div> | |
| <div class="test"><br>אבג | |
| </div> | |
| <div class="test"> | |
| abcאבגdef | |
| </div> | |
| <div class="test"> | |
| אבגabcדהו | |
| </div> | |
| <div class="test"> | |
| abcאבגדהו | |
| </div> | |
| <div class="test"> | |
| אבגabcdef | |
| </div> | |
| <div class="test">١٢٣ابة</div> | |
| <div class="test">ابة١٢٣</div> | |
| <div class="test"> | |
| <span>abc</span>אבגdef | |
| </div> | |
| <div class="test"> | |
| <span>אבג</span>abcדהו | |
| </div> | |
| <div class="test">abcאבג123דהוdef | |
| </div> | |
| <div class="test">abcאבג123 | |
| </div> | |
| <div class="test">abcאבג123def | |
| </div> | |
| <div class="test">אבג123דהוabcזחט456יכל | |
| </div> | |
| <div class="test" style="width: 120px;"> | |
| before אחרי אנציקלופדיה | |
| </div> | |
| <div class="test" style="width: 120px;"> | |
| לפני after encyclopedia | |
| </div> | |
| <div class="test" contenteditable style="width: 120px;"> | |
| before אחרי אנציקלופדיה | |
| </div> | |
| <div class="test" contenteditable style="width: 120px;"> | |
| לפני after encyclopedia | |
| </div> | |
| <div class="test" style="width: 100px;"> | |
| This is יותר צר מיתר the boxes. | |
| </div> | |
| <div contenteditable class="test" style="width: 100px;"> | |
| This is יותר צר מיתר the boxes. | |
| </div> | |
| <div class="test"> | |
| Lorem | |
| <div></div> | |
| ipsum | |
| </div> | |
| <div class="test"> | |
| צלחת | |
| <div></div> | |
| מצנפת | |
| </div> | |
| <div class="test"> | |
| abcdefאבג<img>דהו | |
| </div> | |
| <div class="test"> | |
| אבגדהוabc<img>def | |
| </div> | |
| <div class="test"> | |
| abc<input>אבג<img><img>דהוghi | |
| </div> | |
| <div class="test"> | |
| אבג<input>abc<img><img>defדהו | |
| </div> | |
| <div class="test"> | |
| abcאבג<span>דהו</span> | |
| </div> | |
| <div class="test"> | |
| אבגabc<span>def</span> | |
| </div> | |
| <div class="test"> | |
| ab<span>cאבגdef</span> | |
| </div> | |
| <div class="test"> | |
| אב<span>גabcדהו</span> | |
| </div> | |
| <div class="test"> | |
| abc<span>אבגdef</span> | |
| </div> | |
| <div class="test"> | |
| אבג<span>abcדהו</span> | |
| </div> | |
| <div class="test"> | |
| abcאdef | |
| </div> | |
| <div class="test"> | |
| אבגaדהו | |
| </div> | |
| <div class="test"> | |
| abcאבג<span>def</span> | |
| </div> | |
| <div class="test"> | |
| אבגabc<span>דהו</span> | |
| </div> | |
| <div class="test"> | |
| abcא<span>בגdef</span> | |
| </div> | |
| <div class="test"> | |
| אבגa<span>bcדהו</span> | |
| </div> | |
| <div class="test" style="white-space: pre;">abc<!-- --> | |
| <!-- -->def</div> | |
| <div class="test" style="white-space: pre;">אבג<!-- --> | |
| <!-- -->דהו</div> | |
| <div class="test"> | |
| <span dir="rtl">abcקקק123נננdef</span> | |
| </div> | |
| </div> | |
| <pre id="console"></pre> | |
| </body> |