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

        Continue experiment with moving caret by word in visual order.
        https://bugs.webkit.org/show_bug.cgi?id=57806

        This is the 2nd patch, which adds implementation when caret is inside box
        (not at boundaries). If the word break is inside the same box and not at the boundaries
        either, the word break will be returned. If need to search the adjacent boxes for word
        breaks, then, only the cases implemented in bug 57336 work.

        * editing/visible_units.cpp:
        (WebCore::leftmostPositionInRTLBoxInLTRBlock):
        (WebCore::rightmostPositionInLTRBoxInRTLBlock):
        (WebCore::lastWordBreakInBox):
        (WebCore::positionIsVisuallyOrderedInBoxInBlockWithDifferentDirectionality):
        (WebCore::nextWordBreakInBoxInsideBlockWithDifferentDirectionality):
        (WebCore::WordBoundaryEntry::WordBoundaryEntry):
        (WebCore::collectWordBreaksInBoxInsideBlockWithSameDirectionality):
        (WebCore::collectWordBreaksInBoxInsideBlockWithDifferntDirectionality):
        (WebCore::greatestValueUnder):
        (WebCore::smallestOffsetAbove):
        (WebCore::positionIsInsideBox):
        (WebCore::positionBeforeNextWord):
        (WebCore::positionAfterPreviousWord):
        (WebCore::leftWordPosition):
        (WebCore::rightWordPosition):
2011-04-06  Xiaomei Ji  <xji@chromium.org>

        Reviewed by Ryosuke Niwa.

        Continue experiment with moving caret by word in visual order.
        https://bugs.webkit.org/show_bug.cgi?id=57806

        Return correct result when caret and the word break are inside a box (not at boundaries).
        Added test case to test word break at a random start point
        (not start from a word break).

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

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@83483 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 374c12096cbd99a8b468f12a62c2ad279f91e9f4..70f9a34939f12bad7b3d4680dafeb8ea3fd38d49 100644 (file)
@@ -1,3 +1,16 @@
+2011-04-06  Xiaomei Ji  <xji@chromium.org>
+
+        Reviewed by Ryosuke Niwa.
+
+        Continue experiment with moving caret by word in visual order.
+        https://bugs.webkit.org/show_bug.cgi?id=57806
+
+        Return correct result when caret and the word break are inside a box (not at boundaries).
+        Added test case to test word break at a random start point
+        (not start from a word break).
+
+        * editing/selection/move-by-word-visually-expected.txt:
+
 2011-04-11  Jian Li  <jianli@chromium.org>
 
         Unreviewed, chromium expectations change.
index b36ac288986da12eaa675886df4dda0a8e97e5ff..06f7f4e0b694970a54088b97cce98e92a7f1ea12 100644 (file)
@@ -2,34 +2,34 @@
 ======== Move By Word ====
 Test 1, LTR:
 Move right by one word
-"abc def hij opq rst"[0, 4, 8, 12, 16, 19]    FAIL expected: [4, 8, 12, 16, 19, 19]
+"abc def hij opq rst"[0, 8, 12, 16, 16, 19]    FAIL expected: [4, 8, 12, 16, 19, 19]
 Move left by one word
-"abc def hij opq rst"[16, 16, 12, 8, 4, 0]    FAIL expected: [16, 12, 8, 4, 0, 0]
+"abc def hij opq rst"[16, 12, 8, 4, 0, 0]
 Test 2, RTL:
 Move left by one word
-"abc def hij opq rst"[0, 15, 11, 7, 3, 19]    FAIL expected: [15, 11, 7, 3, 19, 19]
+"abc def hij opq rst"[0, 11, 7, 3, 3, 19]    FAIL expected: [15, 11, 7, 3, 19, 19]
 Move right by one word
-"abc def hij opq rst"[19, 3, 7, 11, 15, 0]    FAIL expected: [3, 7, 11, 15, 0, 0]
+"abc def hij opq rst"[19, 7, 11, 15, 0, 0]    FAIL expected: [3, 7, 11, 15, 0, 0]
 Test 3, LTR:
 Move right by one word
-"ZZZ QQQ BBB CCC XXX"[0, 15, 11, 7, 3, 19]    FAIL expected: [15, 11, 7, 3, 19, 19]
+"ZZZ QQQ BBB CCC XXX"[0, 11, 7, 3, 3, 19]    FAIL expected: [15, 11, 7, 3, 19, 19]
 Move left by one word
-"ZZZ QQQ BBB CCC XXX"[19, 3, 7, 11, 15, 0]    FAIL expected: [3, 7, 11, 15, 0, 0]
+"ZZZ QQQ BBB CCC XXX"[19, 7, 11, 15, 0, 0]    FAIL expected: [3, 7, 11, 15, 0, 0]
 Test 4, RTL:
 Move left by one word
-"ZZZ QQQ BBB CCC XXX"[0, 4, 8, 12, 16, 19]    FAIL expected: [4, 8, 12, 16, 19, 19]
+"ZZZ QQQ BBB CCC XXX"[0, 8, 12, 16, 16, 19]    FAIL expected: [4, 8, 12, 16, 19, 19]
 Move right by one word
-"ZZZ QQQ BBB CCC XXX"[16, 16, 12, 8, 4, 0]    FAIL expected: [16, 12, 8, 4, 0, 0]
+"ZZZ QQQ BBB CCC XXX"[16, 12, 8, 4, 0, 0]
 Test 5, LTR:
 Move right by one word
-"abc def ZQB RIG uvw xyz"[0, 4, 8, 11, 16, 20, 23]    FAIL expected: [4, 8, 11, 16, 20, 23, 23]
+"abc def ZQB RIG uvw xyz"[0, 4, 8, 11, 20, 20, 23]    FAIL expected: [4, 8, 11, 16, 20, 23, 23]
 Move left by one word
-"abc def ZQB RIG uvw xyz"[20, 20, 16, 11, 4, 4, 0]    FAIL expected: [20, 16, 11, 8, 4, 0, 0]
+"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:
 Move left by one word
-"abc def ZQB RIG uvw xyz"[0, 3, 8, 12, 16, 19, 23]    FAIL expected: [3, 8, 12, 16, 19, 23, 23]
+"abc def ZQB RIG uvw xyz"[0, 3, 12, 12, 16, 19, 23]    FAIL expected: [3, 8, 12, 16, 19, 23, 23]
 Move right by one word
-"abc def ZQB RIG uvw xyz"[12, 19, 12, 12, 8, 3, 0]    FAIL expected: [19, 16, 12, 8, 3, 0, 0]
+"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:
 Move right by one word
 "ZQB abc RIG"[0, 4, 8, 11]    FAIL expected: [4, 8, 11, 11]
@@ -39,5 +39,9 @@ Test 8, RTL:
 Move left by one word
 "ZQB abc RIG"[0, 4, 8, 11]    FAIL expected: [4, 8, 11, 11]
 Move right by one word
-"ZQB abc RIG"[8, 8, 0, 0]    FAIL expected: [8, 4, 0, 0]
+"ZQB abc RIG"[8, 0, 0, 0]    FAIL expected: [8, 4, 0, 0]
 
+======== Move By Word Specific Test ====
+Test 1
+Move left by one word
+"BB"[1], "AAA "[0]
index 1f64a16db80fcad29f9473660433c9bed3ebe96d..eaa551691e632baa615f9f68ab9def511dfc07a3 100644 (file)
@@ -158,6 +158,37 @@ function runMoveLeftRight(tests, unit)
         }
 
     }
+}
+
+function runSpecificTest(tests)
+{
+    var sel = getSelection();
+    log("\n======== Move By Word Specific Test ====\n");
+    for (var i = 0; i < tests.length; ++i) {
+        log("Test " + (i + 1) + "\n");
+        var components = tests[i].title.split(" ");
+        var startOffset = components[0];
+        var movingDirection = components[1];
+        var endingNode = components[2];
+        var endingOffset = components[3];
+        log("Move " + movingDirection + " by one word\n");
+
+        var positions = [];
+        sel.setPosition(tests[i].firstChild, startOffset);
+        positions.push({ node: sel.anchorNode, offset: sel.anchorOffset, point: caretCoordinates() });
+
+        sel.modify("move", movingDirection, "-webkit-visual-word");
+        positions.push({ node: sel.anchorNode, offset: sel.anchorOffset, point: caretCoordinates() });
+
+        logPositions(positions);
+
+        if (sel.anchorNode.parentNode != document.getElementById(endingNode) || sel.anchorOffset != endingOffset) {
+            log("    FAIL: expected ");
+            var endingChildNode = document.getElementById(endingNode).firstChild;
+            log((endingChildNode instanceof Text ? '"' + fold(endingChildNode.data) + '"' : "<" + endingChildNode.tagName + ">") + "[" + endingOffset + "]");
+        }
+    }
     document.getElementById("testMoveByWord").style.display = "none";
 }
 
@@ -165,6 +196,9 @@ function runTest() {
     log("\n======== Move By Word ====\n");
     var tests = document.getElementsByClassName("test_move_by_word");
     runMoveLeftRight(tests, "word");
+    
+    tests = document.getElementsByClassName("specific_test");
+    runSpecificTest(tests);
 }
 
 onload = function() {
@@ -191,6 +225,9 @@ The numbers printed out in the output are ending word boundaries. -->
 <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>
 <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>
+
+<!-- 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>
 
 <pre id="console"></pre>
index 777d55c9dc28e4b31a7c9f43fdf82cff56eba1c2..9309ab78a4f2a2f098d51a5ecdcb40b805a4e0e2 100644 (file)
@@ -1,3 +1,32 @@
+2011-04-06  Xiaomei Ji  <xji@chromium.org>
+
+        Reviewed by Ryosuke Niwa.
+
+        Continue experiment with moving caret by word in visual order.
+        https://bugs.webkit.org/show_bug.cgi?id=57806
+
+        This is the 2nd patch, which adds implementation when caret is inside box
+        (not at boundaries). If the word break is inside the same box and not at the boundaries
+        either, the word break will be returned. If need to search the adjacent boxes for word
+        breaks, then, only the cases implemented in bug 57336 work.
+
+        * editing/visible_units.cpp:
+        (WebCore::leftmostPositionInRTLBoxInLTRBlock):
+        (WebCore::rightmostPositionInLTRBoxInRTLBlock):
+        (WebCore::lastWordBreakInBox):
+        (WebCore::positionIsVisuallyOrderedInBoxInBlockWithDifferentDirectionality):
+        (WebCore::nextWordBreakInBoxInsideBlockWithDifferentDirectionality):
+        (WebCore::WordBoundaryEntry::WordBoundaryEntry):
+        (WebCore::collectWordBreaksInBoxInsideBlockWithSameDirectionality):
+        (WebCore::collectWordBreaksInBoxInsideBlockWithDifferntDirectionality):
+        (WebCore::greatestValueUnder):
+        (WebCore::smallestOffsetAbove):
+        (WebCore::positionIsInsideBox):
+        (WebCore::positionBeforeNextWord):
+        (WebCore::positionAfterPreviousWord):
+        (WebCore::leftWordPosition):
+        (WebCore::rightWordPosition):
+
 2011-04-11  Mario Sanchez Prada  <msanchez@igalia.com>
 
         Reviewed by Chris Fleizach.
index fafcdcfb63fa144deb02f7423d2b400d32a9e142..e4a84bab335b944b5b8d8532e25721018dd3bbf9 100644 (file)
@@ -1173,6 +1173,180 @@ static VisiblePosition previousWordBreakInBoxInsideBlockWithSameDirectionality(c
     return wordBreak;
 }
 
+static VisiblePosition leftmostPositionInRTLBoxInLTRBlock(const InlineBox* box)
+{
+    // FIXME: Probably need to take care of bidi level too.
+    Node* node = box->renderer()->node();
+    InlineBox* previousLeaf = box->prevLeafChild();
+    InlineBox* nextLeaf = box->nextLeafChild();   
+    
+    if (previousLeaf && !previousLeaf->isLeftToRightDirection())
+        return Position(node, box->caretMaxOffset(), Position::PositionIsOffsetInAnchor);
+
+    if (nextLeaf && !nextLeaf->isLeftToRightDirection()) {
+        if (previousLeaf)
+            return Position(previousLeaf->renderer()->node(), previousLeaf->caretMaxOffset(), Position::PositionIsOffsetInAnchor);
+
+        InlineBox* lastRTLLeaf;
+        do {
+            lastRTLLeaf = nextLeaf;
+            nextLeaf = nextLeaf->nextLeafChild();
+        } while (nextLeaf && !nextLeaf->isLeftToRightDirection());
+        return Position(lastRTLLeaf->renderer()->node(), lastRTLLeaf->caretMinOffset(), Position::PositionIsOffsetInAnchor);
+    }
+
+    return Position(node, box->caretMinOffset(), Position::PositionIsOffsetInAnchor);
+}
+
+static VisiblePosition rightmostPositionInLTRBoxInRTLBlock(const InlineBox* box)
+{
+    // FIXME: Probably need to take care of bidi level too.
+    Node* node = box->renderer()->node();
+    InlineBox* previousLeaf = box->prevLeafChild();
+    InlineBox* nextLeaf = box->nextLeafChild();   
+    
+    if (nextLeaf && nextLeaf->isLeftToRightDirection())    
+        return Position(node, box->caretMaxOffset(), Position::PositionIsOffsetInAnchor);
+
+    if (previousLeaf && previousLeaf->isLeftToRightDirection()) {
+        if (nextLeaf)
+            return Position(nextLeaf->renderer()->node(), nextLeaf->caretMaxOffset(), Position::PositionIsOffsetInAnchor);
+
+        InlineBox* firstLTRLeaf;
+        do {
+            firstLTRLeaf = previousLeaf;
+            previousLeaf = previousLeaf->prevLeafChild();
+        } while (previousLeaf && previousLeaf->isLeftToRightDirection());
+        return Position(firstLTRLeaf->renderer()->node(), firstLTRLeaf->caretMinOffset(), Position::PositionIsOffsetInAnchor);
+    }
+
+    return Position(node, box->caretMinOffset(), Position::PositionIsOffsetInAnchor);
+}
+    
+static VisiblePosition lastWordBreakInBox(const InlineBox* box, int& offsetOfWordBreak)
+{
+    // Add the leftmost word break for RTL box or rightmost word break for LTR box.
+    InlineBox* previousLeaf = box->prevLeafChild();
+    InlineBox* nextLeaf = box->nextLeafChild();
+    VisiblePosition boundaryPosition;
+    if (box->direction() == RTL && (!previousLeaf || previousLeaf->isLeftToRightDirection()))
+        boundaryPosition = leftmostPositionInRTLBoxInLTRBlock(box);
+    else if (box->direction() == LTR && (!nextLeaf || !nextLeaf->isLeftToRightDirection()))
+        boundaryPosition = rightmostPositionInLTRBoxInRTLBlock(box);
+
+    if (boundaryPosition.isNull())
+        return VisiblePosition();            
+
+    VisiblePosition wordBreak = nextWordPosition(boundaryPosition);
+    if (wordBreak != boundaryPosition)
+        wordBreak = previousWordPosition(wordBreak);
+
+    InlineBox* boxOfWordBreak;
+    wordBreak.getInlineBoxAndOffset(boxOfWordBreak, offsetOfWordBreak);
+    if (boxOfWordBreak == box)
+        return wordBreak;
+    return VisiblePosition();    
+}
+
+static bool positionIsVisuallyOrderedInBoxInBlockWithDifferentDirectionality(const VisiblePosition& wordBreak, const InlineBox* box, int& offsetOfWordBreak)
+{
+    int previousOffset = offsetOfWordBreak;
+    InlineBox* boxOfWordBreak;
+    wordBreak.getInlineBoxAndOffset(boxOfWordBreak, offsetOfWordBreak);
+    if (boxOfWordBreak == box && (previousOffset == invalidOffset || previousOffset < offsetOfWordBreak))
+        return true;
+    return false;
+}
+    
+static VisiblePosition nextWordBreakInBoxInsideBlockWithDifferentDirectionality(
+    const InlineBox* box, const VisiblePosition& previousWordBreak, int& offsetOfWordBreak, bool& isLastWordBreakInBox)
+{
+    // FIXME: Probably need to take care of bidi level too.
+    
+    // In a LTR block, the word break should be on the left boundary of a word.
+    // In a RTL block, the word break should be on the right boundary of a word.
+    // Because previousWordPosition() returns the word break on the right boundary of the word for RTL text,
+    // we need to use nextWordPosition() to traverse words within the inline boxes from right to left to find the next word break.
+    // The same applies to LTR text, in which words are traversed within the inline boxes from left to right.
+    
+    // FIXME: handle multi-spaces (http://webkit.org/b/57543).
+    
+    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)) {
+        isLastWordBreakInBox = false;
+        return wordBreak;
+    }
+    
+    isLastWordBreakInBox = true;
+    return lastWordBreakInBox(box, offsetOfWordBreak);
+}
+
+struct WordBoundaryEntry {
+    WordBoundaryEntry()
+        : offsetInInlineBox(invalidOffset) 
+    { 
+    }
+
+    WordBoundaryEntry(const VisiblePosition& position, int offset)
+        : visiblePosition(position)
+        , offsetInInlineBox(offset) 
+    { 
+    }
+
+    VisiblePosition visiblePosition;
+    int offsetInInlineBox;
+};
+    
+typedef Vector<WordBoundaryEntry, 50> WordBoundaryVector;
+    
+static void collectWordBreaksInBoxInsideBlockWithSameDirectionality(const InlineBox* box, WordBoundaryVector& orderedWordBoundaries)
+{
+    orderedWordBoundaries.clear();
+    
+    VisiblePosition wordBreak;
+    int offsetOfWordBreak = invalidOffset;
+    while (1) {
+        wordBreak = previousWordBreakInBoxInsideBlockWithSameDirectionality(box, wordBreak, offsetOfWordBreak);
+        if (wordBreak.isNull())
+            break;
+        WordBoundaryEntry wordBoundaryEntry(wordBreak, offsetOfWordBreak);
+        orderedWordBoundaries.append(wordBoundaryEntry);
+    }
+}
+
+static void collectWordBreaksInBoxInsideBlockWithDifferntDirectionality(const InlineBox* box, WordBoundaryVector& orderedWordBoundaries)
+{
+    orderedWordBoundaries.clear();
+    
+    VisiblePosition wordBreak;
+    int offsetOfWordBreak = invalidOffset;
+    while (1) {
+        bool isLastWordBreakInBox = false;
+        wordBreak = nextWordBreakInBoxInsideBlockWithDifferentDirectionality(box, wordBreak, offsetOfWordBreak, isLastWordBreakInBox);
+        if (wordBreak.isNotNull()) {
+            WordBoundaryEntry wordBoundaryEntry(wordBreak, offsetOfWordBreak);
+            orderedWordBoundaries.append(wordBoundaryEntry);
+        }
+        if (isLastWordBreakInBox)
+            break;
+    }
+}
+    
 static VisiblePosition previousWordBreakInBox(const InlineBox* box, int offset, TextDirection blockDirection)
 {
     int offsetOfWordBreak = 0;
@@ -1189,6 +1363,44 @@ static VisiblePosition previousWordBreakInBox(const InlineBox* box, int offset,
     return VisiblePosition();
 }
 
+static int greatestValueUnder(int offset, bool boxAndBlockAreInSameDirection, const WordBoundaryVector& orderedWordBoundaries)
+{
+    if (!orderedWordBoundaries.size())
+        return invalidOffset;
+    // FIXME: binary search.
+    if (boxAndBlockAreInSameDirection) {
+        for (unsigned i = 0; i < orderedWordBoundaries.size(); ++i) {
+            if (orderedWordBoundaries[i].offsetInInlineBox < offset)
+                return i;
+        }
+        return invalidOffset;
+    }
+    for (int i = orderedWordBoundaries.size() - 1; i >= 0; --i) {
+        if (orderedWordBoundaries[i].offsetInInlineBox < offset)
+            return i;
+    }
+    return invalidOffset;
+}
+
+static int smallestOffsetAbove(int offset, bool boxAndBlockAreInSameDirection, const WordBoundaryVector& orderedWordBoundaries)
+{
+    if (!orderedWordBoundaries.size())
+        return invalidOffset;
+    // FIXME: binary search.
+    if (boxAndBlockAreInSameDirection) {
+        for (int i = orderedWordBoundaries.size() - 1; i >= 0; --i) {
+            if (orderedWordBoundaries[i].offsetInInlineBox > offset)
+                return i;
+        }
+        return invalidOffset;
+    }
+    for (unsigned i = 0; i < orderedWordBoundaries.size(); ++i) {
+        if (orderedWordBoundaries[i].offsetInInlineBox > offset)
+            return i;
+    }
+    return invalidOffset;
+}
 static VisiblePosition leftWordBoundary(const InlineBox* box, int offset, TextDirection blockDirection)
 {
     VisiblePosition wordBreak;
@@ -1215,7 +1427,33 @@ static VisiblePosition rightWordBoundary(const InlineBox* box, int offset, TextD
     }
     return VisiblePosition();
 }
+    
+static bool positionIsInsideBox(const VisiblePosition& wordBreak, const InlineBox* box)
+{
+    InlineBox* boxOfWordBreak;
+    int offsetOfWordBreak;
+    wordBreak.getInlineBoxAndOffset(boxOfWordBreak, offsetOfWordBreak);
+    return box == boxOfWordBreak && offsetOfWordBreak != box->caretMaxOffset() && offsetOfWordBreak != box->caretMinOffset();
+}
 
+static VisiblePosition positionBeforeNextWord(const VisiblePosition& position)
+{
+    VisiblePosition positionAfterCurrentWord = nextWordPosition(position);
+    VisiblePosition positionAfterNextWord = nextWordPosition(positionAfterCurrentWord);
+    if (positionAfterCurrentWord == positionAfterNextWord)
+        return positionAfterCurrentWord;
+    return previousWordPosition(positionAfterNextWord);
+}
+
+static VisiblePosition positionAfterPreviousWord(const VisiblePosition& position)
+{
+    VisiblePosition positionBeforeCurrentWord = previousWordPosition(position);
+    VisiblePosition positionBeforePreviousWord = previousWordPosition(positionBeforeCurrentWord);
+    if (positionBeforeCurrentWord == positionBeforePreviousWord)
+        return positionBeforeCurrentWord;
+    return nextWordPosition(positionBeforePreviousWord);
+}
+    
 VisiblePosition leftWordPosition(const VisiblePosition& visiblePosition)
 {
     InlineBox* box;
@@ -1231,8 +1469,34 @@ VisiblePosition leftWordPosition(const VisiblePosition& visiblePosition)
     if (offset == box->caretRightmostOffset())
         return leftWordBoundary(box, offset, blockDirection);
     
-    // FIXME: Not implemented.
-    return VisiblePosition();
+    
+    VisiblePosition wordBreak;
+    if (box->direction() == blockDirection) {
+        if (blockDirection == RTL)
+            wordBreak = positionBeforeNextWord(visiblePosition);
+        else
+            wordBreak = previousWordPosition(visiblePosition);
+    } else {
+        if (blockDirection == RTL)
+            wordBreak = positionAfterPreviousWord(visiblePosition);
+        else
+            wordBreak = nextWordPosition(visiblePosition);
+    }
+    if (positionIsInsideBox(wordBreak, box))
+        return wordBreak;
+    
+    WordBoundaryVector orderedWordBoundaries;
+    if (box->direction() == blockDirection)
+        collectWordBreaksInBoxInsideBlockWithSameDirectionality(box, orderedWordBoundaries);
+    else
+        collectWordBreaksInBoxInsideBlockWithDifferntDirectionality(box, orderedWordBoundaries);
+
+    int index = box->isLeftToRightDirection() ? greatestValueUnder(offset, blockDirection == LTR, orderedWordBoundaries) :
+        smallestOffsetAbove(offset, blockDirection == RTL, orderedWordBoundaries);
+    if (index != invalidOffset)
+        return orderedWordBoundaries[index].visiblePosition;
+    
+    return leftWordBoundary(box->prevLeafChild(), invalidOffset, blockDirection);
 }
 
 VisiblePosition rightWordPosition(const VisiblePosition& visiblePosition)
@@ -1245,10 +1509,34 @@ VisiblePosition rightWordPosition(const VisiblePosition& visiblePosition)
     if (offset == box->caretLeftmostOffset())
         return rightWordBoundary(box, offset, blockDirection);
     if (offset == box->caretRightmostOffset())
-        return rightWordBoundary(box->nextLeafChild(), -1, blockDirection);
+        return rightWordBoundary(box->nextLeafChild(), invalidOffset, blockDirection);
+    VisiblePosition wordBreak;
+    if (box->direction() == blockDirection) {
+        if (blockDirection == LTR)
+            wordBreak = positionBeforeNextWord(visiblePosition);
+        else
+            wordBreak = previousWordPosition(visiblePosition);
+    } else {
+        if (blockDirection == LTR)
+            wordBreak = positionAfterPreviousWord(visiblePosition);
+        else
+            wordBreak = nextWordPosition(visiblePosition);
+    } 
+    if (positionIsInsideBox(wordBreak, box))
+        return wordBreak;
     
-    // FIXME: Not implemented.
-    return VisiblePosition();
+    WordBoundaryVector orderedWordBoundaries;
+    if (box->direction() == blockDirection)
+        collectWordBreaksInBoxInsideBlockWithSameDirectionality(box, orderedWordBoundaries);
+    else
+        collectWordBreaksInBoxInsideBlockWithDifferntDirectionality(box, orderedWordBoundaries);
+    int index = box->isLeftToRightDirection() ? smallestOffsetAbove(offset, blockDirection == LTR, orderedWordBoundaries) :
+        greatestValueUnder(offset, blockDirection == RTL, orderedWordBoundaries);
+    if (index != invalidOffset)
+        return orderedWordBoundaries[index].visiblePosition;
+    
+    return rightWordBoundary(box->nextLeafChild(), invalidOffset, blockDirection);
 }
 
 }