2011-04-20 Xiaomei Ji <xji@chromium.org>
authorxji@chromium.org <xji@chromium.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 20 Apr 2011 22:20:39 +0000 (22:20 +0000)
committerxji@chromium.org <xji@chromium.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 20 Apr 2011 22:20:39 +0000 (22:20 +0000)
        Reviewed by Ryosuke Niwa.

        Continue (3rd) experiment with moving caret by word in visual order.
        https://bugs.webkit.org/show_bug.cgi?id=58294

        This patch along with r82588 and r83483 implements moving caret by
        word in visual order.

        The overall algorithm is:
        1. First get the InlineBox and offset of the pass-in VisiblePosition.
        2. Based on the position (left boundary, middle, right boundary) of the offset and the
           direction of the movement, look for visually adjacent word breaks.
        2.1 If the offset is the minimum offset of the box,
            return the rightmost word boundary in previous boxes if moving left.
            return the leftmost word boundary in box and next boxes if moving right.
        2.2 Similar for the case when offset is at the maximum offset of the box.
        2.3 When offset is inside the box (not at boundaries), first find the previousWordPosition
            or nextWordPosition based on the directionality of the box. If this word break position
            is also inside the same box, return it. Otherwise (the nextWordPosition or
            previousWordPosition is not in the same box or is at the box boundary), collect all the
            word breaks in the box and search for the one closest to the input "offset" based on
            box directionality, block directionality, and movement direction. Continue search in
            adjacent boxes if needed.

        Notes:
        1. Word boundaries are collected one box at a time. Only when a boundary that is closest to
           the input position (in the moving direction) is not available in current box, word
           boundaries in adjacent box will be collected. So, there is no need to save InlineBox in
           word boundaries. Instead, the word boundaries are saved as a pair
           (VisiblePosition, offset) to avoid recomputing VisiblePosition.

        2. We only collect boundaries of the right kind (i.e. left boundary of a word in LTR block
           and right boundary of a word in RTL block). And word boundaries are collected using
           previousWordPosition() and nextWordPosition(). So when box directionality is the same as
           block directionality, word boundaries are collected from right to left visually in a LTR
           box, and word boundaries are collected from left to right visually in a RTL box. It is
           the other way around when box directionality is different from block directionality.

        3. To find the right kinds of word boundaries, we must move back and forth between words
           in some situations. For example, if we're moving to the right in a LTR box in LTR block,
           we cannot simply return nextWordPosition() because it would return the right boundary
           of a word. Instead, we return nextWordPosition()'s nextWordPosition()'s previousWordPosition().

        4. When collecting word breaks inside a box, it first computes a start position, then
           collect the right kind of word breaks until it reaches the end of (or beyond) the box.
           In the meanwhile, it might need special handling on the rightmost or leftmost position
           based on the directionality of the box and block. These computations do not consider the
           box's bidi level.

        * editing/visible_units.cpp:
        (WebCore::nextWordBreakInBoxInsideBlockWithDifferentDirectionality):
        (WebCore::collectWordBreaksInBox):
        (WebCore::previousWordBoundaryInBox):
        (WebCore::nextWordBoundaryInBox):
        (WebCore::visuallyLastWordBoundaryInBox):
        (WebCore::leftWordBoundary):
        (WebCore::rightWordBoundary):
        (WebCore::leftWordPosition):
        (WebCore::rightWordPosition):
2011-04-20  Xiaomei Ji  <xji@chromium.org>

        Reviewed by Ryosuke Niwa.

        Continue (3rd) experiment with moving caret by word in visual order.
        https://bugs.webkit.org/show_bug.cgi?id=58294

        * editing/selection/move-by-word-visually-expected.txt:
        * editing/selection/move-by-word-visually.html:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@84425 268f45cc-cd09-0410-ab3c-d52691b4dbfc

LayoutTests/ChangeLog
LayoutTests/editing/selection/move-by-word-visually-expected.txt
LayoutTests/editing/selection/move-by-word-visually.html
Source/WebCore/ChangeLog
Source/WebCore/editing/visible_units.cpp

index 0a27a2a..dffe261 100644 (file)
@@ -1,3 +1,13 @@
+2011-04-20  Xiaomei Ji  <xji@chromium.org>
+
+        Reviewed by Ryosuke Niwa.
+
+        Continue (3rd) experiment with moving caret by word in visual order.
+        https://bugs.webkit.org/show_bug.cgi?id=58294
+
+        * editing/selection/move-by-word-visually-expected.txt:
+        * editing/selection/move-by-word-visually.html:
+
 2011-04-20  Cris Neckar  <cdn@chromium.org>
 
         Reviewed by Dirk Schulze.
index 06f7f4e..f94f22e 100644 (file)
 ======== Move By Word ====
 Test 1, LTR:
 Move right by one word
-"abc def hij opq rst"[0, 8, 12, 16, 16, 19]    FAIL expected: [4, 8, 12, 16, 19, 19]
+"abc def hij opq rst"[0, 4, 8, 12, 16]
 Move left by one word
-"abc def hij opq rst"[16, 12, 8, 4, 0, 0]
-Test 2, RTL:
+"abc def hij opq rst"[19, 16, 12, 8, 4, 0]
+Test 2, LTR:
+Move right by one word
+"abc def hij opq rst"[0, 4, 8, 12, 16]
+Move left by one word
+"abc def hij opq rst"[19, 16, 12, 8, 4, 0]
+Test 3, RTL:
+Move left by one word
+"abc def hij opq rst"[0, 15, 11, 7, 3]
+Move right by one word
+"abc def hij opq rst"[19, 3, 7, 11, 15, 0]
+Test 4, LTR:
+Move right by one word
+"ZZZ QQQ BBB CCC XXX"[0, 15, 11, 7, 3]
+Move left by one word
+"ZZZ QQQ BBB CCC XXX"[19, 3, 7, 11, 15, 0]
+Test 5, RTL:
+Move left by one word
+"ZZZ QQQ BBB CCC XXX"[0, 4, 8, 12, 16]
+Move right by one word
+"ZZZ QQQ BBB CCC XXX"[19, 16, 12, 8, 4, 0]
+Test 6, LTR:
+Move right by one word
+"abc def hij AAA BBB WWW opr uvw xyz"[0, 4, 8, 12, 19, 15, 24, 28, 32]
+Move left by one word
+"abc def hij AAA BBB WWW opr uvw xyz"[35, 32, 28, 24, 15, 19, 12, 8, 4, 0]
+Test 7, RTL:
+Move left by one word
+"abc def hij AAA BBB WWW opr uvw xyz"[0, 7, 3, 12, 16, 20, 24, 31, 27]
+Move right by one word
+"abc def hij AAA BBB WWW opr uvw xyz"[35, 27, 31, 24, 20, 16, 12, 3, 7, 0]
+Test 8, LTR:
+Move right by one word
+"abc def ZQB RIG uvw xyz"[0, 4, 8, 11, 16, 20]
+Move left by one word
+"abc def ZQB RIG uvw xyz"[23, 20, 16, 11, 8, 4, 0]
+Test 9, RTL:
+Move left by one word
+"abc def ZQB RIG uvw xyz"[0, 3, 8, 12, 16, 19]
+Move right by one word
+"abc def ZQB RIG uvw xyz"[23, 19, 16, 12, 8, 3, 0]
+Test 10, LTR:
+Move right by one word
+"aaa AAA bbb"[0, 4, 8]
+Move left by one word
+"aaa AAA bbb"[11, 8, 4, 0]
+Test 11, RTL:
+Move left by one word
+"aaa AAA bbb"[0, 4, 8, 11]
+Move right by one word
+"aaa AAA bbb"[11, 8, 4, 0]
+Test 12, LTR:
+Move right by one word
+"AAA BBB WWW aaa bbb ccc DDD SSS UUU"[0, 7, 3, 12, 16, 20, 24, 31, 27]
+Move left by one word
+"AAA BBB WWW aaa bbb ccc DDD SSS UUU"[35, 27, 31, 24, 20, 16, 12, 3, 7, 0]
+Test 13, RTL:
+Move left by one word
+"AAA BBB WWW aaa bbb ccc DDD SSS UUU"[0, 4, 8, 12, 19, 15, 24, 28, 32]
+Move right by one word
+"AAA BBB WWW aaa bbb ccc DDD SSS UUU"[35, 32, 28, 24, 15, 19, 12, 8, 4, 0]
+Test 14, LTR:
+Move right by one word
+"AAA BBB aaa bbb WWW DDD"[0, 3, 8, 12, 16, 19]
+Move left by one word
+"AAA BBB aaa bbb WWW DDD"[23, 19, 16, 12, 8, 3, 0]
+Test 15, RTL:
+Move left by one word
+"AAA BBB aaa bbb WWW DDD"[0, 4, 8, 11, 16, 20]
+Move right by one word
+"AAA BBB aaa bbb WWW DDD"[23, 20, 16, 11, 8, 4, 0]
+Test 16, LTR:
+Move right by one word
+"ZQB abc RIG"[0, 4, 8, 11]
 Move left by one word
-"abc def hij opq rst"[0, 11, 7, 3, 3, 19]    FAIL expected: [15, 11, 7, 3, 19, 19]
+"ZQB abc RIG"[11, 8, 4, 0]
+Test 17, RTL:
+Move left by one word
+"ZQB abc RIG"[0, 4, 8]
+Move right by one word
+"ZQB abc RIG"[11, 8, 4, 0]
+Test 18, LTR:
 Move right by one word
-"abc def hij opq rst"[19, 7, 11, 15, 0, 0]    FAIL expected: [3, 7, 11, 15, 0, 0]
-Test 3, LTR:
+"abc def    hij opq"[0, 4, 15]    FAIL expected: [0, 4, 11, 15]
+Move left by one word
+"abc def    hij opq"[18, 15, 4, 0]    FAIL expected: [18, 15, 11, 4, 0]
+Test 19, LTR:
 Move right by one word
-"ZZZ QQQ BBB CCC XXX"[0, 11, 7, 3, 3, 19]    FAIL expected: [15, 11, 7, 3, 19, 19]
+"AAA "[0, 3]
 Move left by one word
-"ZZZ QQQ BBB CCC XXX"[19, 7, 11, 15, 0, 0]    FAIL expected: [3, 7, 11, 15, 0, 0]
-Test 4, RTL:
+"BB"[2], "AAA "[3, 0]
+Test 20, RTL:
 Move left by one word
-"ZZZ QQQ BBB CCC XXX"[0, 8, 12, 16, 16, 19]    FAIL expected: [4, 8, 12, 16, 19, 19]
+"AAA "[0, 4]
 Move right by one word
-"ZZZ QQQ BBB CCC XXX"[16, 12, 8, 4, 0, 0]
-Test 5, LTR:
+"BB"[2], "AAA "[4, 0]
+Test 21, LTR:
 Move right by one word
-"abc def ZQB RIG uvw xyz"[0, 4, 8, 11, 20, 20, 23]    FAIL expected: [4, 8, 11, 16, 20, 23, 23]
+"abc def "[0, 4, 8], "hij opq"[4], " rst uvw"[1, 5]
+Move left by one word
+" rst uvw"[8, 5, 1], "hij opq"[4], "abc def "[8, 4, 0]
+Test 22, RTL:
 Move left by one word
-"abc def ZQB RIG uvw xyz"[20, 16, 4, 8, 4, 0, 0]    FAIL expected: [20, 16, 11, 8, 4, 0, 0]
-Test 6, RTL:
+"abc def "[0], " rst uvw"[4], "hij opq"[3], "abc def "[7, 3]    FAIL expected: ["abc def "[ 0, ]" rst uvw"[ 4,  0, ]"hij opq"[ 3, ]"abc def "[ 7,  3]
+Move right by one word
+" rst uvw"[8], "abc def "[3, 7], "hij opq"[3], " rst uvw"[4], "abc def "[0]    FAIL expected: [" rst uvw"[ 8, ]"abc def "[ 3,  7, ]"hij opq"[ 3, ]" rst uvw"[ 0,  4, ]"abc def "[ 0]
+Test 23, RTL:
 Move left by one word
-"abc def ZQB RIG uvw xyz"[0, 3, 12, 12, 16, 19, 23]    FAIL expected: [3, 8, 12, 16, 19, 23, 23]
+"abc def "[0], " rst uvw"[4], "hij opq"[3], "abc def "[7, 3]    FAIL expected: ["abc def "[ 0, ]" rst uvw"[ 4,  0, ]"hij opq"[ 3, ]"abc def "[ 7,  3]
 Move right by one word
-"abc def ZQB RIG uvw xyz"[12, 16, 12, 8, 8, 0, 0]    FAIL expected: [19, 16, 12, 8, 3, 0, 0]
-Test 7, LTR:
+" rst uvw"[8], "abc def "[3, 7], "hij opq"[3], " rst uvw"[4], "abc def "[0]    FAIL expected: [" rst uvw"[ 8, ]"abc def "[ 3,  7, ]"hij opq"[ 3, ]" rst uvw"[ 0,  4, ]"abc def "[ 0]
+Test 24, LTR:
 Move right by one word
-"ZQB abc RIG"[0, 4, 8, 11]    FAIL expected: [4, 8, 11, 11]
+"abc def "[0, 4, 8], "hij opq"[4], " rst uvw"[1, 5]
 Move left by one word
-"ZQB abc RIG"[4, 4, 4, 0]    FAIL expected: [8, 4, 0, 0]
-Test 8, RTL:
+" rst uvw"[8, 5, 1], "hij opq"[4], "abc def "[8, 4, 0]
+Test 25, RTL:
 Move left by one word
-"ZQB abc RIG"[0, 4, 8, 11]    FAIL expected: [4, 8, 11, 11]
+"ABD DSU "[0, 4, 8], "EJH FUX"[4], "FFZ LIG"[4]
+Move right by one word
+"FFZ LIG"[7, 4], "EJH FUX"[4], "ABD DSU "[8, 4, 0]
+Test 26, LTR:
 Move right by one word
-"ZQB abc RIG"[8, 0, 0, 0]    FAIL expected: [8, 4, 0, 0]
+"ABD DSU "[0], "FFZ LIG"[3], "EJH FUX"[3], "ABD DSU "[7, 3]
+Move left by one word
+"FFZ LIG"[7], "ABD DSU "[3, 7], "EJH FUX"[3], "FFZ LIG"[3], "ABD DSU "[0]
+Test 27, RTL:
+Move left by one word
+"ABD DSU "[0, 4, 8], "EJH FUX"[4], "FFZ LIG"[4]
+Move right by one word
+"FFZ LIG"[7, 4], "EJH FUX"[4], "ABD DSU "[8, 4, 0]
+Test 28, LTR:
+Move right by one word
+"ABD DSU "[0], "FFZ LIG"[3], "EJH FUX"[3], "ABD DSU "[7, 3]
+Move left by one word
+"FFZ LIG"[7], "ABD DSU "[3, 7], "EJH FUX"[3], "FFZ LIG"[3], "ABD DSU "[0]
+Test 29, RTL:
+Move left by one word
+"ABD DSU "[0, 4, 8], "abc def"[3], "FFZ LIG"[4]
+Move right by one word
+"FFZ LIG"[7, 4], "abc def"[3], "ABD DSU "[8, 4, 0]
+Test 30, LTR:
+Move right by one word
+"ABD DSU "[0], "FFZ LIG"[3], "ABD DSU "[8], "abc def"[4], "ABD DSU "[7, 3]
+Move left by one word
+"FFZ LIG"[7], "ABD DSU "[3, 7], "abc def"[4], "ABD DSU "[8], "FFZ LIG"[3]    FAIL expected: ["FFZ LIG"[ 7, ]"ABD DSU "[ 3,  7, ]"abc def"[ 4, ]"ABD DSU "[ 8, ]"FFZ LIG"[ 3, ]"ABD DSU "[ 0]
+Test 31, RTL:
+Move left by one word
+"ABD DSU "[0, 4, 8], "abc def"[3], "FFZ LIG"[4]
+Move right by one word
+"FFZ LIG"[7, 4], "abc def"[3], "ABD DSU "[8, 4, 0]
+Test 32, LTR:
+Move right by one word
+"ABD DSU "[0, 3, 8], "abc def"[4], "FFZ LIG"[3]
+Move left by one word
+"FFZ LIG"[7, 3], "abc def"[4], "ABD DSU "[8, 4, 0]
+Test 33, RTL:
+Move left by one word
+"ABD opq DSU "[0, 4, 8], "abc AAA def"[8, 4, 3], "FFZ rst LIG"[4, 8]    FAIL expected: ["ABD opq DSU "[ 0,  4,  8,  12, ]"abc AAA def"[ 4,  3, ]"FFZ rst LIG"[ 4,  8]
+Move right by one word
+"FFZ rst LIG"[11, 8, 4], "abc AAA def"[4, 8], "ABD opq DSU "[8, 4, 0]    FAIL expected: ["FFZ rst LIG"[ 11,  8,  4, ]"abc AAA def"[ 3,  4, ]"ABD opq DSU "[ 12,  8,  4,  0]
+Test 34, LTR:
+Move right by one word
+"ABD opq DSU "[0, 4], "abc AAA def"[8, 4], "ABD opq DSU "[12, 11], "FFZ rst LIG"[4, 8, 11]    FAIL expected: ["ABD opq DSU "[ 0,  4,  8, ]"abc AAA def"[ 8,  7, ]"ABD opq DSU "[ 12,  11, ]"FFZ rst LIG"[ 4,  8,  11]
+Move left by one word
+"FFZ rst LIG"[11, 8, 4], "ABD opq DSU "[11, 12], "abc AAA def"[7, 8], "ABD opq DSU "[7, 4, 0]    FAIL expected: ["FFZ rst LIG"[ 11,  8,  4, ]"ABD opq DSU "[ 11,  12, ]"abc AAA def"[ 7,  8, ]"ABD opq DSU "[ 8,  4,  0]
+Test 35, RTL:
+Move left by one word
+"ABD opq DSU "[0, 4, 8], "abc AAA def"[4, 8], "FFZ rst LIG"[4, 8]    FAIL expected: ["ABD opq DSU "[ 0,  4,  8,  12, ]"abc AAA def"[ 4,  8, ]"FFZ rst LIG"[ 4,  8]
+Move right by one word
+"FFZ rst LIG"[11, 8, 4], "abc AAA def"[8, 4], "ABD opq DSU "[11, 8, 4, 0]    FAIL expected: ["FFZ rst LIG"[ 11,  8,  4, ]"abc AAA def"[ 8,  4, ]"ABD opq DSU "[ 12,  8,  4,  0]
+Test 36, LTR:
+Move right by one word
+"ABD opq DSU "[0, 4, 8, 12], "abc AAA def"[4, 8], "FFZ rst LIG"[4, 8, 11]
+Move left by one word
+"FFZ rst LIG"[11, 8, 4], "abc AAA def"[8, 4], "ABD opq DSU "[12, 8, 4, 0]
+Test 37, LTR:
+Move right by one word
+"aaa "[0, 4], "bbb AAA "[4, 7]
+Move left by one word
+"FFZ"[3], "bbb AAA "[7, 4], "aaa "[4, 0]
 
 ======== Move By Word Specific Test ====
 Test 1
index eaa5516..66f8acf 100644 (file)
@@ -32,15 +32,6 @@ 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 fold(string)
 {
     var result = "";
@@ -72,36 +63,100 @@ function logPositions(positions)
     log("]");
 }
 
+function nodeOfWordBreak(nodeAndOffset)
+{
+    var node = document.getElementById(nodeAndOffset[0]).firstChild;
+    if (nodeAndOffset.length == 3) {
+        var childIndex = nodeAndOffset[2];
+        for (var i = 0; i < childIndex - 1; ++i) {
+            node = node.nextSibling;
+        }
+    }
+    return node;
+}
+
 var wordBreaks;
 
+function logWordBreak(index)
+{
+    var wordBreak = wordBreaks[index];
+    if (wordBreak.search(',') == -1)
+        log(wordBreak);
+    else {
+        var nodeAndOffset = wordBreak.split(',');
+        var node = nodeOfWordBreak(nodeAndOffset);
+
+        var differentNode = false;
+        if (index != 0) {
+            differentNode = nodeOfWordBreak(nodeAndOffset) != nodeOfWordBreak(wordBreaks[index - 1].split(','));
+        
+        }
+        if (differentNode == true)
+            log("]");
+        if (index == 0 || differentNode == true)
+            log((node instanceof Text ? '"' + fold(node.data) + '"' : "<" + node.tagName + ">") + "[");
+        log(nodeAndOffset[1]);
+    }
+}
+
+function positionEqualToWordBreak(position, wordBreak)
+{
+    if (wordBreak.search(',') == -1)
+        return position.offset == wordBreak;
+    else {
+        var nodeAndOffset = wordBreak.split(',');
+        return position.node == nodeOfWordBreak(nodeAndOffset) && position.offset == nodeAndOffset[1];
+    }
+}
+
 function validateData(positions)
 {
+    var equal = true;
+    if (positions.length != wordBreaks.length)
+        equal = false;
     for (var i = 0; i < wordBreaks.length - 1; ++i) {
-        if (positions[i].offset != wordBreaks[i + 1]) {
+        if (!positionEqualToWordBreak(positions[i], wordBreaks[i])) {
+            equal = false;
             break;
         }
     }
-    if (i != wordBreaks.length - 1 && positions[i] != wordBreaks[i]) {
+    if (equal == false) {
         log("    FAIL expected: [");
-        for (var i = 1; i < wordBreaks.length; ++i) {
-            log(wordBreaks[i] + ", ");
+        for (var i = 0; i < wordBreaks.length; ++i) {
+            logWordBreak(i);
+            if (i != wordBreaks.length - 1)
+                log(", ");
         }
-        log(wordBreaks[wordBreaks.length - 1] + "]");
+        log("]");
     }
 }
 
 function collectWordBreaks(test, searchDirection)
 {
-    if (searchDirection == "right") {
-        if (test.getAttribute("dir") == 'ltr') 
-            wordBreaks = test.title.split("|")[0].split(" ");
-        else 
-            wordBreaks = test.title.split("|")[1].split(" ");
-    } else {
-        if (test.getAttribute("dir") == 'ltr') 
-            wordBreaks = test.title.split("|")[1].split(" ");
-        else 
-            wordBreaks = test.title.split("|")[0].split(" ");
+    var title;
+    if (searchDirection == "right") 
+        title = test.title.split("|")[0];
+    else
+        title = test.title.split("|")[1];
+
+    var pattern = /\[(.+?)\]/g;
+    var result;
+    wordBreaks = [];
+    while ((result = pattern.exec(title)) != null) {
+        wordBreaks.push(result[1]);
+    }
+    if (wordBreaks.length == 0) {
+        wordBreaks = title.split(" ");
+    }
+}
+
+function setPosition(sel, node, wordBreak)
+{
+    if (wordBreak.search(',') == -1)
+        sel.setPosition(node, wordBreak);
+    else {
+        var nodeAndOffset = wordBreak.split(',');
+        sel.setPosition(nodeOfWordBreak(nodeAndOffset), nodeAndOffset[1]);
     }
 }
 
@@ -109,18 +164,20 @@ function moveByWord(sel, test, searchDirection, dir)
 {
     log("Move " + searchDirection + " by one word\n");
     var prevOffset = sel.anchorOffset;
-    var node = sel.anchorNode;
-    collectWordBreaks(test, searchDirection);
-    sel.setPosition(node, wordBreaks[0]);
+    var prevNode = sel.anchorNode;
     var positions = [];
-    for (var index = 1; index < wordBreaks.length; ++index) {
+    positions.push({ node: sel.anchorNode, offset: sel.anchorOffset });
+
+    while (1) {
         sel.modify("move", searchDirection, "-webkit-visual-word");
-        positions.push({ node: sel.anchorNode, offset: sel.anchorOffset, point: caretCoordinates() });
-        sel.setPosition(node, wordBreaks[index]);
-    }
-    sel.modify("move", searchDirection, "-webkit-visual-word");
-    positions.push({ node: sel.anchorNode, offset: sel.anchorOffset, point: caretCoordinates() });
+        if (prevNode == sel.anchorNode && prevOffset == sel.anchorOffset)
+            break;
+        positions.push({ node: sel.anchorNode, offset: sel.anchorOffset });
+        prevNode = sel.anchorNode;
+        prevOffset = sel.anchorOffset;
+    };
     logPositions(positions);
+    collectWordBreaks(test, searchDirection);
     validateData(positions);
     log("\n");
 }
@@ -133,6 +190,8 @@ function moveByWordForEveryPosition(sel, test, dir)
     if (dir == "rtl")
         direction = "left";    
     moveByWord(sel, test, direction, dir);    
+
+    sel.modify("move", "forward", "lineBoundary");
     // Check ctrl-left-arrow works for every position.
     if (dir == "ltr")
         direction = "left";
@@ -176,10 +235,10 @@ function runSpecificTest(tests)
 
         var positions = [];
         sel.setPosition(tests[i].firstChild, startOffset);
-        positions.push({ node: sel.anchorNode, offset: sel.anchorOffset, point: caretCoordinates() });
+        positions.push({ node: sel.anchorNode, offset: sel.anchorOffset });
 
         sel.modify("move", movingDirection, "-webkit-visual-word");
-        positions.push({ node: sel.anchorNode, offset: sel.anchorOffset, point: caretCoordinates() });
+        positions.push({ node: sel.anchorNode, offset: sel.anchorOffset });
 
         logPositions(positions);
 
@@ -215,19 +274,104 @@ if (window.layoutTestController)
 </head>
 <body>
 <div id="testMoveByWord">
-<!-- The numbers put in title are starting word boundaries.
-The numbers printed out in the output are ending word boundaries. -->
-<div dir=ltr class="test_move_by_word" title="0 4 8 12 16 19|19 16 12 8 4 0" contenteditable>abc def hij opq rst</div>
-<div dir=rtl class="test_move_by_word" title="0 15 11 7 3 19|19 3 7 11 15 0" contenteditable>abc def hij opq rst</div>
-<div dir=ltr class="test_move_by_word" title="0 15 11 7 3 19|19 3 7 11 15 0" contenteditable>ששש נננ בבב גגג קקק</div>
-<div dir=rtl class="test_move_by_word" title="0 4 8 12 16 19|19 16 12 8 4 0" contenteditable>ששש נננ בבב גגג קקק</div>
-<div dir=ltr class="test_move_by_word" title="0 4 8 11 16 20 23|23 20 16 11 8 4 0" contenteditable>abc def שנב סטז uvw xyz</div>
-<div dir=rtl class="test_move_by_word" title="0 3 8 12 16 19 23|23 19 16 12 8 3 0" contenteditable>abc def שנב סטז uvw xyz</div>
+<!-- 
+Title saves the word breaks.
+The format of title is "xxx|xxxx".
+
+The sequence on the left of "|" is word boundaries when moving caret from left to right.
+The sequence on the right of "|" is word boundaries when moving caret from right to left.
+
+If there is a single node in the line, the sequence are offsets.
+If there are multiple nodes in the line, the sequence is array of [anchor_node_id, offset, child_node_index], 
+where child_node_index is optional, default is the first child of the anchor node.
+-->
+<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>
+
+<!-- pure English -->
+<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>
+<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>
+
+<!-- pure Hebrew -->
+<div dir=ltr class="test_move_by_word" title="0 15 11 7 3|19 3 7 11 15 0" contenteditable>ששש נננ בבב גגג קקק</div>
+<div dir=rtl class="test_move_by_word" title="19 16 12 8 4 0|0 4 8 12 16" contenteditable>ששש נננ בבב גגג קקק</div>
+
+<!-- bidi text -->
+<!-- English Hebrew English -->
+<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>
+<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>
+
+<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>
+<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>
+
+<div dir=ltr class="test_move_by_word" title="0 4 8|11 8 4 0" contenteditable>aaa אאא bbb</div>
+<div dir=rtl class="test_move_by_word" title="11 8 4 0|0 4 8 11" contenteditable>aaa אאא bbb</div>
+
+<!-- Hebrew English Hebrew -->
+<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>
+<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>
+
+<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>
+<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>
+
 <div dir=ltr class="test_move_by_word" title="0 4 8 11|11 8 4 0" contenteditable>שנב abc סטז</div>
-<div dir=rtl class="test_move_by_word" title="0 4 8 11|11 8 4 0" contenteditable>שנב abc סטז</div>
+<div dir=rtl class="test_move_by_word" title="11 8 4 0|0 4 8" contenteditable>שנב abc סטז</div>
+
+<!-- multiple spaces between word. 
+     FAILED: word break between "def" and "hij" is unreachable.
+-->
+<div dir=ltr class="test_move_by_word" title="0 4 11 15|18 15 11 4 0" contenteditable>abc def    hij opq</div>
+
+<!-- Inline element -->
+<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>
+<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>
+
+<!-- pure English in inline element with same or different directionality from its parent -->
+<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>
+
+<!-- FAILED -->
+<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][div_4, 0, 3][div_4, 4, 3][div_4, 0, 1]|[div_4, 0, 1][div_4, 4, 3][div_4, 0, 3][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>
+
+<!-- FAILED -->
+<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][div_5, 0, 3][div_5, 4, 3][div_5, 0, 1]|[div_5, 0, 1][div_5, 4, 3][div_5, 0, 3][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>
+
+<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>
+
+<!-- pure Hebrew in inline element with same or different directionality from its parent -->
+
+<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>
+
+<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>
+
+<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>
+
+<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>
+
+<!-- bidi in inline element with same or different directionality from its parent -->
+<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>
+
+<!-- FAIL -->
+<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>
+
+<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>
+
+<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, 4, 1][div_14, 0, 1]" contenteditable>אבד דעפ <span id="span_14">abc def</span>ווש כטז</div>
+
+<!-- FAILED -->
+<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>
+
+<!-- FAILED, and wrong printing result -->
+<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>
+
+<!-- FAILED -->
+<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>
+
+<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>
+
+<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>
 
 <!-- The content in title means "startOffsetInCurrentNode movingDirectionByWord endingNode endingOffsetInEndingNode" -->
-<div id="div_1" contenteditable>אאא <span id="span_1" class="specific_test" title="1 left div_1 0">בב</span></div>
+<div id="div_100" contenteditable>אאא <span id="span_100" class="specific_test" title="1 left div_100 0">בב</span></div>
 </div>
 
 <pre id="console"></pre>
index 8766819..682d19d 100644 (file)
@@ -1,3 +1,65 @@
+2011-04-20  Xiaomei Ji  <xji@chromium.org>
+
+        Reviewed by Ryosuke Niwa.
+
+        Continue (3rd) experiment with moving caret by word in visual order.
+        https://bugs.webkit.org/show_bug.cgi?id=58294
+
+        This patch along with r82588 and r83483 implements moving caret by
+        word in visual order.
+        
+        The overall algorithm is:
+        1. First get the InlineBox and offset of the pass-in VisiblePosition.
+        2. Based on the position (left boundary, middle, right boundary) of the offset and the
+           direction of the movement, look for visually adjacent word breaks.
+        2.1 If the offset is the minimum offset of the box,
+            return the rightmost word boundary in previous boxes if moving left.
+            return the leftmost word boundary in box and next boxes if moving right.
+        2.2 Similar for the case when offset is at the maximum offset of the box.
+        2.3 When offset is inside the box (not at boundaries), first find the previousWordPosition 
+            or nextWordPosition based on the directionality of the box. If this word break position 
+            is also inside the same box, return it. Otherwise (the nextWordPosition or 
+            previousWordPosition is not in the same box or is at the box boundary), collect all the 
+            word breaks in the box and search for the one closest to the input "offset" based on 
+            box directionality, block directionality, and movement direction. Continue search in 
+            adjacent boxes if needed.
+
+        Notes:
+        1. Word boundaries are collected one box at a time. Only when a boundary that is closest to 
+           the input position (in the moving direction) is not available in current box, word 
+           boundaries in adjacent box will be collected. So, there is no need to save InlineBox in 
+           word boundaries. Instead, the word boundaries are saved as a pair 
+           (VisiblePosition, offset) to avoid recomputing VisiblePosition.
+        2. We only collect boundaries of the right kind (i.e. left boundary of a word in LTR block
+           and right boundary of a word in RTL block). And word boundaries are collected using 
+           previousWordPosition() and nextWordPosition(). So when box directionality is the same as 
+           block directionality, word boundaries are collected from right to left visually in a LTR 
+           box, and word boundaries are collected from left to right visually in a RTL box. It is
+           the other way around when box directionality is different from block directionality.
+
+        3. To find the right kinds of word boundaries, we must move back and forth between words
+           in some situations. For example, if we're moving to the right in a LTR box in LTR block,
+           we cannot simply return nextWordPosition() because it would return the right boundary
+           of a word. Instead, we return nextWordPosition()'s nextWordPosition()'s previousWordPosition().
+
+        4. When collecting word breaks inside a box, it first computes a start position, then
+           collect the right kind of word breaks until it reaches the end of (or beyond) the box.
+           In the meanwhile, it might need special handling on the rightmost or leftmost position 
+           based on the directionality of the box and block. These computations do not consider the 
+           box's bidi level.
+
+        * editing/visible_units.cpp:
+        (WebCore::nextWordBreakInBoxInsideBlockWithDifferentDirectionality):
+        (WebCore::collectWordBreaksInBox):
+        (WebCore::previousWordBoundaryInBox):
+        (WebCore::nextWordBoundaryInBox):
+        (WebCore::visuallyLastWordBoundaryInBox):
+        (WebCore::leftWordBoundary):
+        (WebCore::rightWordBoundary):
+        (WebCore::leftWordPosition):
+        (WebCore::rightWordPosition):
+
 2011-04-20  Cris Neckar  <cdn@chromium.org>
 
         Reviewed by Dirk Schulze.
index e4a84ba..c55ff9b 100644 (file)
@@ -1274,20 +1274,14 @@ static VisiblePosition nextWordBreakInBoxInsideBlockWithDifferentDirectionality(
     bool hasSeenWordBreakInThisBox = previousWordBreak.isNotNull();
     VisiblePosition wordBreak = hasSeenWordBreakInThisBox ? previousWordBreak : Position(box->renderer()->node(), box->caretMinOffset(), Position::PositionIsOffsetInAnchor);
     wordBreak = nextWordPosition(wordBreak);
-    
-    if (wordBreak == previousWordBreak) {
-        isLastWordBreakInBox = true;
-        return VisiblePosition();
-    }
-
-
+  
     // Given RTL box "ABC DEF" either follows a LTR box or is the first visual box in an LTR block as an example,
     // the visual display of the RTL box is: "(0)J(10)I(9)H(8) (7)F(6)E(5)D(4) (3)C(2)B(1)A(11)",
     // where the number in parenthesis represents offset in visiblePosition. 
     // Start at offset 0, the first word break is at offset 3, the 2nd word break is at offset 7, and the 3rd word break should be at offset 0.
     // But nextWordPosition() of offset 7 is offset 11, which should be ignored, 
     // and the position at offset 0 should be manually added as the last word break within the box.
-    if (positionIsVisuallyOrderedInBoxInBlockWithDifferentDirectionality(wordBreak, box, offsetOfWordBreak)) {
+    if (wordBreak != previousWordBreak && positionIsVisuallyOrderedInBoxInBlockWithDifferentDirectionality(wordBreak, box, offsetOfWordBreak)) {
         isLastWordBreakInBox = false;
         return wordBreak;
     }
@@ -1346,15 +1340,21 @@ static void collectWordBreaksInBoxInsideBlockWithDifferntDirectionality(const In
             break;
     }
 }
+
+static void collectWordBreaksInBox(const InlineBox* box, WordBoundaryVector& orderedWordBoundaries, TextDirection blockDirection)
+{
+    if (box->direction() == blockDirection)
+        collectWordBreaksInBoxInsideBlockWithSameDirectionality(box, orderedWordBoundaries);
+    else
+        collectWordBreaksInBoxInsideBlockWithDifferntDirectionality(box, orderedWordBoundaries);        
+}
     
-static VisiblePosition previousWordBreakInBox(const InlineBox* box, int offset, TextDirection blockDirection)
+static VisiblePosition previousWordBoundaryInBox(const InlineBox* box, int offset)
 {
     int offsetOfWordBreak = 0;
     VisiblePosition wordBreak;
     while (true) {
-        if (box->direction() == blockDirection)
-            wordBreak = previousWordBreakInBoxInsideBlockWithSameDirectionality(box, wordBreak, offsetOfWordBreak);
-        // FIXME: Implement the 'else' case when the box direction is not equal to the block direction.
+        wordBreak = previousWordBreakInBoxInsideBlockWithSameDirectionality(box, wordBreak, offsetOfWordBreak);
         if (wordBreak.isNull())
             break;
         if (offset == invalidOffset || offsetOfWordBreak != offset)
@@ -1363,6 +1363,32 @@ static VisiblePosition previousWordBreakInBox(const InlineBox* box, int offset,
     return VisiblePosition();
 }
 
+static VisiblePosition nextWordBoundaryInBox(const InlineBox* box, int offset)
+{
+    int offsetOfWordBreak = 0;
+    VisiblePosition wordBreak;
+    bool isLastWordBreakInBox = false;
+    do {
+        wordBreak = nextWordBreakInBoxInsideBlockWithDifferentDirectionality(box, wordBreak, offsetOfWordBreak, isLastWordBreakInBox);
+        if (wordBreak.isNotNull() && (offset == invalidOffset || offsetOfWordBreak != offset))
+            return wordBreak;
+    } while (!isLastWordBreakInBox);       
+    return VisiblePosition();
+}
+    
+static VisiblePosition visuallyLastWordBoundaryInBox(const InlineBox* box, int offset, TextDirection blockDirection)
+{
+    WordBoundaryVector orderedWordBoundaries;
+    collectWordBreaksInBox(box, orderedWordBoundaries, blockDirection);
+    if (!orderedWordBoundaries.size()) 
+        return VisiblePosition();
+    if (offset == invalidOffset || orderedWordBoundaries[orderedWordBoundaries.size() - 1].offsetInInlineBox != offset)
+        return orderedWordBoundaries[orderedWordBoundaries.size() - 1].visiblePosition;
+    if (orderedWordBoundaries.size() > 1)
+        return orderedWordBoundaries[orderedWordBoundaries.size() - 2].visiblePosition;
+    return VisiblePosition();
+}
+        
 static int greatestValueUnder(int offset, bool boxAndBlockAreInSameDirection, const WordBoundaryVector& orderedWordBoundaries)
 {
     if (!orderedWordBoundaries.size())
@@ -1400,14 +1426,18 @@ static int smallestOffsetAbove(int offset, bool boxAndBlockAreInSameDirection, c
     }
     return invalidOffset;
 }
+
 static VisiblePosition leftWordBoundary(const InlineBox* box, int offset, TextDirection blockDirection)
 {
     VisiblePosition wordBreak;
     for  (const InlineBox* adjacentBox = box; adjacentBox; adjacentBox = adjacentBox->prevLeafChild()) {
-        if (blockDirection == LTR) 
-            wordBreak = previousWordBreakInBox(adjacentBox, adjacentBox == box ? offset : invalidOffset, blockDirection);
-        // FIXME: Implement the "else" case.
+        if (blockDirection == LTR) {
+            if (box->isLeftToRightDirection()) 
+                wordBreak = previousWordBoundaryInBox(adjacentBox, adjacentBox == box ? offset : invalidOffset);
+            else
+                wordBreak = nextWordBoundaryInBox(adjacentBox, adjacentBox == box ? offset : invalidOffset);
+        } else 
+            wordBreak = visuallyLastWordBoundaryInBox(adjacentBox, adjacentBox == box ? offset : invalidOffset, blockDirection);            
         if (wordBreak.isNotNull())
             return wordBreak;
     }
@@ -1419,9 +1449,13 @@ static VisiblePosition rightWordBoundary(const InlineBox* box, int offset, TextD
     
     VisiblePosition wordBreak;
     for (const InlineBox* adjacentBox = box; adjacentBox; adjacentBox = adjacentBox->nextLeafChild()) {
-        if (blockDirection == RTL)
-            wordBreak = previousWordBreakInBox(adjacentBox, adjacentBox == box ? offset : invalidOffset, blockDirection);
-        // FIXME: Implement the "else" case.
+        if (blockDirection == RTL) {
+            if (box->isLeftToRightDirection())
+                wordBreak = nextWordBoundaryInBox(adjacentBox, adjacentBox == box ? offset : invalidOffset);
+            else
+                wordBreak = previousWordBoundaryInBox(adjacentBox, adjacentBox == box ? offset : invalidOffset);
+        } else 
+            wordBreak = visuallyLastWordBoundaryInBox(adjacentBox, adjacentBox == box ? offset : invalidOffset, blockDirection);            
         if (!wordBreak.isNull())
             return wordBreak;
     }
@@ -1486,10 +1520,7 @@ VisiblePosition leftWordPosition(const VisiblePosition& visiblePosition)
         return wordBreak;
     
     WordBoundaryVector orderedWordBoundaries;
-    if (box->direction() == blockDirection)
-        collectWordBreaksInBoxInsideBlockWithSameDirectionality(box, orderedWordBoundaries);
-    else
-        collectWordBreaksInBoxInsideBlockWithDifferntDirectionality(box, orderedWordBoundaries);
+    collectWordBreaksInBox(box, orderedWordBoundaries, blockDirection);
 
     int index = box->isLeftToRightDirection() ? greatestValueUnder(offset, blockDirection == LTR, orderedWordBoundaries) :
         smallestOffsetAbove(offset, blockDirection == RTL, orderedWordBoundaries);
@@ -1527,10 +1558,8 @@ VisiblePosition rightWordPosition(const VisiblePosition& visiblePosition)
         return wordBreak;
     
     WordBoundaryVector orderedWordBoundaries;
-    if (box->direction() == blockDirection)
-        collectWordBreaksInBoxInsideBlockWithSameDirectionality(box, orderedWordBoundaries);
-    else
-        collectWordBreaksInBoxInsideBlockWithDifferntDirectionality(box, orderedWordBoundaries);
+    collectWordBreaksInBox(box, orderedWordBoundaries, blockDirection);
+    
     int index = box->isLeftToRightDirection() ? smallestOffsetAbove(offset, blockDirection == LTR, orderedWordBoundaries) :
         greatestValueUnder(offset, blockDirection == RTL, orderedWordBoundaries);
     if (index != invalidOffset)