LayoutTests:
authorharrison <harrison@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 13 Mar 2007 22:31:43 +0000 (22:31 +0000)
committerharrison <harrison@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 13 Mar 2007 22:31:43 +0000 (22:31 +0000)
        Reviewed by Justin.

        <rdar://problem/5031181> cntl-k at end of paragraph adds nothing to the kill ring
        <rdar://problem/5031189> REGRESSION: cntl-y yanks only the most recently killed content

        * editing/pasteboard/emacs-ctrl-k-y-001-expected.checksum: Added.
        * editing/pasteboard/emacs-ctrl-k-y-001-expected.png: Added.
        * editing/pasteboard/emacs-ctrl-k-y-001-expected.txt: Added.
        * editing/pasteboard/emacs-ctrl-k-y-001.html: Added.

WebCore:

        Reviewed by Justin.

        <rdar://problem/5031181> cntl-k at end of paragraph adds nothing to the kill ring
        <rdar://problem/5031189> REGRESSION: cntl-y yanks only the most recently killed content

        For rdar://5031181, properly extend the selection before the killring handling, and
        make sure plainText of that selection returns a linefeed.

        For rdar://5031189, restore Editor::deleteRange() code that continued current killring,
        even though the range deletion implicitly stopped it via changing the selection.

        A byproduct of this change is the elimination of RUNDFINDER vs CONTENT TextIterator. The
        only difference between the two was whether to emit a newline when the range started
        with a blockflow element. No callers actually need that any more.

        Tests added:
        * editing/pasteboard/emacs-ctrl-k-y-001-expected.checksum: Added.
        * editing/pasteboard/emacs-ctrl-k-y-001-expected.png: Added.
        * editing/pasteboard/emacs-ctrl-k-y-001-expected.txt: Added.
        * editing/pasteboard/emacs-ctrl-k-y-001.html: Added.

        * editing/Editor.cpp:
        (WebCore::Editor::deleteRange):
        Clear the "start new kill ring sequence" setting, because it was set to true
        when the selection was updated by deleting the range.

        (WebCore::Editor::deleteWithDirection):
        If extending the selection to the end of paragraph resulted in a caret selection,
        extend by character, to handle the case when the selection started as a caret at
        the end of paragraph.

        * editing/TextIterator.cpp:
        (WebCore::TextIterator::TextIterator):
        Initialize new member variables for tracking handling of the beginning of the range.

        (WebCore::TextIterator::advance):
        Call representNodeOffsetZero on the m_endContainer.
        Move visibility checks into handleTextNode and handleReplacedElement.

        (WebCore::TextIterator::handleTextNode):
        (WebCore::TextIterator::handleTextBox):
        Call emitText.

        (WebCore::TextIterator::handleReplacedElement):
        Moved visibility check into here.

        (WebCore::shouldEmitNewlinesBeforeAndAfterNode):

        (WebCore::TextIterator::shouldRepresentNodeOffsetZero):
        (WebCore::TextIterator::representNodeOffsetZero):
        New. Emits proper sequence when encountering offset 0 of a node, including the
        m_endContainer. Started with code from handleNonTextNode.

        (WebCore::TextIterator::handleNonTextNode):
        Call representNodeOffsetZero.

        (WebCore::TextIterator::exitNode):
        Similar to shouldRepresentNodeOffsetZero, do not emit the newline if the node
        was collapsed, and before any other emitted content.

        (WebCore::TextIterator::emitCharacter):

        (WebCore::TextIterator::emitText):
        New. Consolidates code used by handleText and handleTextBox.

        (WebCore::CharacterIterator::CharacterIterator):
        Removed RUNFINDER.

        (WebCore::WordAwareIterator::WordAwareIterator):
        Removed RUNFINDER.

        (WebCore::WordAwareIterator::advance):
        Formatting.

        (WebCore::TextIterator::rangeLength):
        Formatting.

        * editing/TextIterator.h:
        Added member variables for tracking handling of the beginning of the range.
        Eliminated concept of RUNDFINDER vs CONTENT TextIterator.

        * editing/visible_units.cpp:
        (WebCore::nextBoundary):
        Eliminated concept of RUNDFINDER vs CONTENT TextIterator.

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

LayoutTests/ChangeLog
LayoutTests/editing/pasteboard/emacs-ctrl-k-y-001-expected.checksum [new file with mode: 0644]
LayoutTests/editing/pasteboard/emacs-ctrl-k-y-001-expected.png [new file with mode: 0644]
LayoutTests/editing/pasteboard/emacs-ctrl-k-y-001-expected.txt [new file with mode: 0644]
LayoutTests/editing/pasteboard/emacs-ctrl-k-y-001.html [new file with mode: 0644]
WebCore/ChangeLog
WebCore/editing/Editor.cpp
WebCore/editing/TextIterator.cpp
WebCore/editing/TextIterator.h
WebCore/editing/visible_units.cpp

index 62adecffd9efdbb9d91a871876aa0321443c1016..5652ed75ac4218f8db790ca77b038760986f88bc 100644 (file)
@@ -1,3 +1,15 @@
+2007-03-13  David Harrison  <harrison@apple.com>
+
+        Reviewed by Justin.
+
+        <rdar://problem/5031181> cntl-k at end of paragraph adds nothing to the kill ring
+        <rdar://problem/5031189> REGRESSION: cntl-y yanks only the most recently killed content
+
+        * editing/pasteboard/emacs-ctrl-k-y-001-expected.checksum: Added.
+        * editing/pasteboard/emacs-ctrl-k-y-001-expected.png: Added.
+        * editing/pasteboard/emacs-ctrl-k-y-001-expected.txt: Added.
+        * editing/pasteboard/emacs-ctrl-k-y-001.html: Added.
+
 2007-03-13  Antti Koivisto  <antti@apple.com>
 
         Reviewed by Darin.
diff --git a/LayoutTests/editing/pasteboard/emacs-ctrl-k-y-001-expected.checksum b/LayoutTests/editing/pasteboard/emacs-ctrl-k-y-001-expected.checksum
new file mode 100644 (file)
index 0000000..eef5b7b
--- /dev/null
@@ -0,0 +1 @@
+759805c2c075cdc457d860b20cd9eb78
\ No newline at end of file
diff --git a/LayoutTests/editing/pasteboard/emacs-ctrl-k-y-001-expected.png b/LayoutTests/editing/pasteboard/emacs-ctrl-k-y-001-expected.png
new file mode 100644 (file)
index 0000000..43adeb8
Binary files /dev/null and b/LayoutTests/editing/pasteboard/emacs-ctrl-k-y-001-expected.png differ
diff --git a/LayoutTests/editing/pasteboard/emacs-ctrl-k-y-001-expected.txt b/LayoutTests/editing/pasteboard/emacs-ctrl-k-y-001-expected.txt
new file mode 100644 (file)
index 0000000..50e6f5b
--- /dev/null
@@ -0,0 +1,91 @@
+EDITING DELEGATE: shouldBeginEditingInDOMRange:range from 0 of DIV > BODY > HTML > #document to 3 of DIV > BODY > HTML > #document
+EDITING DELEGATE: webViewDidChangeSelection:WebViewDidChangeSelectionNotification
+EDITING DELEGATE: webViewDidChangeSelection:WebViewDidChangeSelectionNotification
+EDITING DELEGATE: webViewDidChangeSelection:WebViewDidChangeSelectionNotification
+EDITING DELEGATE: webViewDidChangeSelection:WebViewDidChangeSelectionNotification
+EDITING DELEGATE: shouldDeleteDOMRange:range from 8 of #text > DIV > DIV > BODY > HTML > #document to 14 of #text > DIV > DIV > BODY > HTML > #document
+EDITING DELEGATE: shouldChangeSelectedDOMRange:range from 8 of #text > DIV > DIV > BODY > HTML > #document to 8 of #text > DIV > DIV > BODY > HTML > #document toDOMRange:range from 8 of #text > DIV > DIV > BODY > HTML > #document to 8 of #text > DIV > DIV > BODY > HTML > #document affinity:NSSelectionAffinityDownstream stillSelecting:FALSE
+EDITING DELEGATE: webViewDidChangeSelection:WebViewDidChangeSelectionNotification
+EDITING DELEGATE: webViewDidChange:WebViewDidChangeNotification
+EDITING DELEGATE: webViewDidChangeSelection:WebViewDidChangeSelectionNotification
+EDITING DELEGATE: shouldDeleteDOMRange:range from 8 of #text > DIV > DIV > BODY > HTML > #document to 0 of DIV > DIV > DIV > BODY > HTML > #document
+EDITING DELEGATE: webViewDidChangeSelection:WebViewDidChangeSelectionNotification
+EDITING DELEGATE: shouldChangeSelectedDOMRange:(null) toDOMRange:range from 8 of #text > DIV > DIV > BODY > HTML > #document to 8 of #text > DIV > DIV > BODY > HTML > #document affinity:NSSelectionAffinityDownstream stillSelecting:FALSE
+EDITING DELEGATE: webViewDidChangeSelection:WebViewDidChangeSelectionNotification
+EDITING DELEGATE: webViewDidChange:WebViewDidChangeNotification
+EDITING DELEGATE: webViewDidChangeSelection:WebViewDidChangeSelectionNotification
+EDITING DELEGATE: shouldDeleteDOMRange:range from 0 of #text > DIV > DIV > BODY > HTML > #document to 13 of #text > DIV > DIV > BODY > HTML > #document
+EDITING DELEGATE: webViewDidChangeSelection:WebViewDidChangeSelectionNotification
+EDITING DELEGATE: shouldChangeSelectedDOMRange:(null) toDOMRange:range from 8 of #text > DIV > DIV > BODY > HTML > #document to 8 of #text > DIV > DIV > BODY > HTML > #document affinity:NSSelectionAffinityDownstream stillSelecting:FALSE
+EDITING DELEGATE: webViewDidChangeSelection:WebViewDidChangeSelectionNotification
+EDITING DELEGATE: webViewDidChange:WebViewDidChangeNotification
+EDITING DELEGATE: webViewDidChangeSelection:WebViewDidChangeSelectionNotification
+EDITING DELEGATE: shouldDeleteDOMRange:range from 8 of #text > DIV > DIV > BODY > HTML > #document to 0 of DIV > DIV > DIV > BODY > HTML > #document
+EDITING DELEGATE: webViewDidChangeSelection:WebViewDidChangeSelectionNotification
+EDITING DELEGATE: shouldChangeSelectedDOMRange:(null) toDOMRange:range from 8 of #text > DIV > DIV > BODY > HTML > #document to 8 of #text > DIV > DIV > BODY > HTML > #document affinity:NSSelectionAffinityDownstream stillSelecting:FALSE
+EDITING DELEGATE: webViewDidChangeSelection:WebViewDidChangeSelectionNotification
+EDITING DELEGATE: webViewDidChange:WebViewDidChangeNotification
+EDITING DELEGATE: webViewDidChangeSelection:WebViewDidChangeSelectionNotification
+EDITING DELEGATE: shouldDeleteDOMRange:range from 0 of #text > DIV > DIV > BODY > HTML > #document to 16 of #text > DIV > DIV > BODY > HTML > #document
+EDITING DELEGATE: webViewDidChangeSelection:WebViewDidChangeSelectionNotification
+EDITING DELEGATE: shouldChangeSelectedDOMRange:(null) toDOMRange:range from 8 of #text > DIV > DIV > BODY > HTML > #document to 8 of #text > DIV > DIV > BODY > HTML > #document affinity:NSSelectionAffinityDownstream stillSelecting:FALSE
+EDITING DELEGATE: webViewDidChangeSelection:WebViewDidChangeSelectionNotification
+EDITING DELEGATE: webViewDidChange:WebViewDidChangeNotification
+EDITING DELEGATE: shouldInsertText: three
+four five six
+seven eight nine replacingDOMRange:range from 8 of #text > DIV > DIV > BODY > HTML > #document to 8 of #text > DIV > DIV > BODY > HTML > #document givenAction:WebViewInsertActionTyped
+EDITING DELEGATE: shouldChangeSelectedDOMRange:range from 8 of #text > DIV > DIV > BODY > HTML > #document to 8 of #text > DIV > DIV > BODY > HTML > #document toDOMRange:range from 14 of #text > DIV > DIV > BODY > HTML > #document to 14 of #text > DIV > DIV > BODY > HTML > #document affinity:NSSelectionAffinityDownstream stillSelecting:FALSE
+EDITING DELEGATE: webViewDidChangeSelection:WebViewDidChangeSelectionNotification
+EDITING DELEGATE: webViewDidChange:WebViewDidChangeNotification
+EDITING DELEGATE: shouldChangeSelectedDOMRange:range from 14 of #text > DIV > DIV > BODY > HTML > #document to 14 of #text > DIV > DIV > BODY > HTML > #document toDOMRange:range from 0 of DIV > DIV > BODY > HTML > #document to 0 of DIV > DIV > BODY > HTML > #document affinity:NSSelectionAffinityDownstream stillSelecting:FALSE
+EDITING DELEGATE: webViewDidChangeSelection:WebViewDidChangeSelectionNotification
+EDITING DELEGATE: webViewDidChange:WebViewDidChangeNotification
+EDITING DELEGATE: webViewDidChangeSelection:WebViewDidChangeSelectionNotification
+EDITING DELEGATE: shouldChangeSelectedDOMRange:(null) toDOMRange:range from 13 of #text > DIV > DIV > BODY > HTML > #document to 13 of #text > DIV > DIV > BODY > HTML > #document affinity:NSSelectionAffinityDownstream stillSelecting:FALSE
+EDITING DELEGATE: webViewDidChangeSelection:WebViewDidChangeSelectionNotification
+EDITING DELEGATE: webViewDidChange:WebViewDidChangeNotification
+EDITING DELEGATE: shouldChangeSelectedDOMRange:range from 13 of #text > DIV > DIV > BODY > HTML > #document to 13 of #text > DIV > DIV > BODY > HTML > #document toDOMRange:range from 0 of DIV > DIV > BODY > HTML > #document to 0 of DIV > DIV > BODY > HTML > #document affinity:NSSelectionAffinityDownstream stillSelecting:FALSE
+EDITING DELEGATE: webViewDidChangeSelection:WebViewDidChangeSelectionNotification
+EDITING DELEGATE: webViewDidChange:WebViewDidChangeNotification
+EDITING DELEGATE: webViewDidChangeSelection:WebViewDidChangeSelectionNotification
+EDITING DELEGATE: shouldChangeSelectedDOMRange:(null) toDOMRange:range from 16 of #text > DIV > DIV > BODY > HTML > #document to 16 of #text > DIV > DIV > BODY > HTML > #document affinity:NSSelectionAffinityDownstream stillSelecting:FALSE
+EDITING DELEGATE: webViewDidChangeSelection:WebViewDidChangeSelectionNotification
+EDITING DELEGATE: webViewDidChange:WebViewDidChangeNotification
+layer at (0,0) size 800x600
+  RenderView at (0,0) size 800x600
+layer at (0,0) size 800x600
+  RenderBlock {HTML} at (0,0) size 800x600
+    RenderBody {BODY} at (8,8) size 784x584
+      RenderBlock {DIV} at (0,0) size 784x212 [border: (2px solid #0000FF)]
+        RenderBlock {DIV} at (14,14) size 756x56
+          RenderText {#text} at (0,0) size 67x28
+            text run at (0,0) width 67: "Tests: "
+          RenderBR {BR} at (0,0) size 0x0
+          RenderText {#text} at (0,28) size 348x28
+            text run at (0,28) width 348: "Multiple ctrl-k presses then a ctrl-y."
+        RenderBlock {DIV} at (14,86) size 756x112
+          RenderBlock (anonymous) at (0,0) size 756x28
+            RenderText {#text} at (0,0) size 189x28
+              text run at (0,0) width 189: "Expected Results: "
+            RenderBR {BR} at (189,22) size 0x0
+          RenderBlock {DIV} at (0,28) size 756x84
+            RenderBlock (anonymous) at (0,0) size 756x28
+              RenderText {#text} at (0,0) size 132x28
+                text run at (0,0) width 132: "one two three"
+            RenderBlock {DIV} at (0,28) size 756x28
+              RenderText {#text} at (0,0) size 118x28
+                text run at (0,0) width 118: "four five six"
+            RenderBlock {DIV} at (0,56) size 756x28
+              RenderText {#text} at (0,0) size 158x28
+                text run at (0,0) width 158: "seven eight nine"
+      RenderBlock {DIV} at (0,236) size 784x96
+        RenderBlock {DIV} at (0,0) size 784x32 [border: (2px solid #FF0000)]
+          RenderText {#text} at (2,2) size 132x28
+            text run at (2,2) width 132: "one two three"
+        RenderBlock {DIV} at (0,32) size 784x32 [border: (2px solid #FF0000)]
+          RenderText {#text} at (2,2) size 118x28
+            text run at (2,2) width 118: "four five six"
+        RenderBlock {DIV} at (0,64) size 784x32 [border: (2px solid #FF0000)]
+          RenderText {#text} at (2,2) size 158x28
+            text run at (2,2) width 158: "seven eight nine"
+caret: position 16 of child 0 {#text} of child 3 {DIV} of child 3 {DIV} of child 1 {BODY} of child 0 {HTML} of document
diff --git a/LayoutTests/editing/pasteboard/emacs-ctrl-k-y-001.html b/LayoutTests/editing/pasteboard/emacs-ctrl-k-y-001.html
new file mode 100644 (file)
index 0000000..4ef85b6
--- /dev/null
@@ -0,0 +1,73 @@
+<html>
+<head>
+<style>
+.editing { 
+    border: 2px solid red; 
+    font-size: 24px; 
+}
+.explanation { 
+    border: 2px solid blue; 
+    padding: 12px; 
+    font-size: 24px; 
+    margin-bottom: 24px;
+}
+.scenario { margin-bottom: 16px;}
+.scenario:first-line { font-weight: bold; margin-bottom: 16px;}
+.expected-results:first-line { font-weight: bold }
+</style>
+
+<script src='../editing.js'></script>
+
+<script>
+function editingTest()
+{
+    moveSelectionForwardByWordCommand(); // cursor after "one"
+    moveSelectionForwardByWordCommand(); // cursor after "two"
+    if (window.eventSender) {
+        eventSender.clearKillRing();
+        eventSender.keyDown("k", ["ctrlKey"]); // two three in the kill ring
+        eventSender.keyDown("k", ["ctrlKey"]); // newline added to killring
+        eventSender.keyDown("k", ["ctrlKey"]); // four five six added to kill ring
+        eventSender.keyDown("k", ["ctrlKey"]); // newline added to killring
+        eventSender.keyDown("k", ["ctrlKey"]); // seven eight nine added to kill ring
+        eventSender.keyDown("y", ["ctrlKey"]);
+    }
+}
+</script>
+<title>Editing Test for ctrl-k and ctrl-y</title>
+</head>
+<body>
+
+<div class="explanation">
+<div class="scenario">
+Tests: 
+<br>
+Multiple ctrl-k presses then a ctrl-y.
+</div>
+<div class="expected-results">
+Expected Results:
+<br>
+
+<div>
+one two three
+<div>four five six</div>
+<div>seven eight nine</div>
+</div>
+
+</div>
+</div>
+
+<div contenteditable id="root" style="word-wrap: break-word; -khtml-nbsp-mode: space; -khtml-line-break: after-white-space;">
+<div id="test" class="editing">
+one two three
+<div>four five six</div>
+<div>seven eight nine</div>
+</div>
+</div>
+
+<script>
+runEditingTest();
+</script>
+
+</body>
+</html>
\ No newline at end of file
index 32779c695eb057508942b5ea57d9ce25335bec83..668423721a703c6e7d308b6ea3f1c5c26b407f53 100644 (file)
@@ -1,3 +1,90 @@
+2007-03-13  David Harrison  <harrison@apple.com>
+
+        Reviewed by Justin.
+
+        <rdar://problem/5031181> cntl-k at end of paragraph adds nothing to the kill ring
+        <rdar://problem/5031189> REGRESSION: cntl-y yanks only the most recently killed content
+
+        For rdar://5031181, properly extend the selection before the killring handling, and
+        make sure plainText of that selection returns a linefeed.
+        
+        For rdar://5031189, restore Editor::deleteRange() code that continued current killring,
+        even though the range deletion implicitly stopped it via changing the selection.
+        
+        A byproduct of this change is the elimination of RUNDFINDER vs CONTENT TextIterator. The
+        only difference between the two was whether to emit a newline when the range started
+        with a blockflow element. No callers actually need that any more.
+
+        Tests added:
+        * editing/pasteboard/emacs-ctrl-k-y-001-expected.checksum: Added.
+        * editing/pasteboard/emacs-ctrl-k-y-001-expected.png: Added.
+        * editing/pasteboard/emacs-ctrl-k-y-001-expected.txt: Added.
+        * editing/pasteboard/emacs-ctrl-k-y-001.html: Added.
+
+        * editing/Editor.cpp:
+        (WebCore::Editor::deleteRange):
+        Clear the "start new kill ring sequence" setting, because it was set to true
+        when the selection was updated by deleting the range.
+        
+        (WebCore::Editor::deleteWithDirection):
+        If extending the selection to the end of paragraph resulted in a caret selection,
+        extend by character, to handle the case when the selection started as a caret at
+        the end of paragraph.
+        
+        * editing/TextIterator.cpp:
+        (WebCore::TextIterator::TextIterator):
+        Initialize new member variables for tracking handling of the beginning of the range.
+        
+        (WebCore::TextIterator::advance):
+        Call representNodeOffsetZero on the m_endContainer.
+        Move visibility checks into handleTextNode and handleReplacedElement.
+        
+        (WebCore::TextIterator::handleTextNode):
+        (WebCore::TextIterator::handleTextBox):
+        Call emitText.
+        
+        (WebCore::TextIterator::handleReplacedElement):
+        Moved visibility check into here.
+        
+        (WebCore::shouldEmitNewlinesBeforeAndAfterNode):
+        
+        (WebCore::TextIterator::shouldRepresentNodeOffsetZero):
+        (WebCore::TextIterator::representNodeOffsetZero):
+        New. Emits proper sequence when encountering offset 0 of a node, including the
+        m_endContainer. Started with code from handleNonTextNode.
+        
+        (WebCore::TextIterator::handleNonTextNode):
+        Call representNodeOffsetZero.
+        
+        (WebCore::TextIterator::exitNode):
+        Similar to shouldRepresentNodeOffsetZero, do not emit the newline if the node
+        was collapsed, and before any other emitted content.
+        
+        (WebCore::TextIterator::emitCharacter):
+        
+        (WebCore::TextIterator::emitText):
+        New. Consolidates code used by handleText and handleTextBox.
+        
+        (WebCore::CharacterIterator::CharacterIterator):
+        Removed RUNFINDER.
+
+        (WebCore::WordAwareIterator::WordAwareIterator):
+        Removed RUNFINDER.
+        
+        (WebCore::WordAwareIterator::advance):
+        Formatting.
+
+        (WebCore::TextIterator::rangeLength):
+        Formatting.
+        
+        * editing/TextIterator.h:
+        Added member variables for tracking handling of the beginning of the range.
+        Eliminated concept of RUNDFINDER vs CONTENT TextIterator.
+        
+        * editing/visible_units.cpp:
+        (WebCore::nextBoundary):
+        Eliminated concept of RUNDFINDER vs CONTENT TextIterator.
+
 2007-03-13  David Hyatt  <hyatt@apple.com>
 
         Clean up the null image case in CachedImage::data to make sure the size totals will stay accurate.
index 441a18457e53025c77a01c5e0fe9d341f900d1ac..b77ff64975cbcf833b74133df1fe766df89858b3 100644 (file)
@@ -237,6 +237,11 @@ void Editor::deleteRange(Range* range, bool killRing, bool prepend, bool smartDe
             }
             break;
     }
+
+    // clear the "start new kill ring sequence" setting, because it was set to true
+    // when the selection was updated by deleting the range
+    if (killRing)
+        setStartNewKillRingSequence(false);
 }
 
 bool Editor::deleteWithDirection(SelectionController::EDirection direction, TextGranularity granularity, bool killRing, bool isTypingAction)
@@ -261,6 +266,9 @@ bool Editor::deleteWithDirection(SelectionController::EDirection direction, Text
         SelectionController selectionController;
         selectionController.setSelection(m_frame->selectionController()->selection());
         selectionController.modify(SelectionController::EXTEND, direction, granularity);
+        if (killRing && selectionController.isCaret() && granularity != CharacterGranularity)
+            selectionController.modify(SelectionController::EXTEND, direction, CharacterGranularity);
+
         range = selectionController.toRange();
         
         switch (direction) {
index a9eb359aa1dff86c071c6bc24844ecd683ab50a0..ba0a62cfaf79ba708e14c68f6cf5f3c416fe11d0 100644 (file)
@@ -37,6 +37,7 @@
 #include "Range.h"
 #include "RenderTableCell.h"
 #include "RenderTableRow.h"
+#include "visible_units.h"
 
 using namespace std;
 using namespace WTF::Unicode;
@@ -73,17 +74,17 @@ private:
     CircularSearchBuffer &operator=(const CircularSearchBuffer&);
 };
 
-TextIterator::TextIterator() : m_endContainer(0), m_endOffset(0), m_positionNode(0), m_lastCharacter(0)
+TextIterator::TextIterator() : m_startContainer(0), m_startOffset(0), m_endContainer(0), m_endOffset(0), m_positionNode(0), m_lastCharacter(0)
 {
 }
 
-TextIterator::TextIterator(const Range *r, IteratorKind kind) : m_endContainer(0), m_endOffset(0), m_positionNode(0)
+TextIterator::TextIterator(const Range *r) : m_startContainer(0), m_startOffset(0), m_endContainer(0), m_endOffset(0), m_positionNode(0)
 {
     if (!r)
         return;
 
     ExceptionCode ec = 0;
-
+    
     // get and validate the range endpoints
     Node *startContainer = r->startContainer(ec);
     int startOffset = r->startOffset(ec);
@@ -96,15 +97,17 @@ TextIterator::TextIterator(const Range *r, IteratorKind kind) : m_endContainer(0
     // the case, we could consider changing this assertion to an early return.
     ASSERT(r->boundaryPointsValid());
 
-    // remember ending place - this does not change
+    // remember range - this does not change
+    m_startContainer = startContainer;
+    m_startOffset = startOffset;
     m_endContainer = endContainer;
     m_endOffset = endOffset;
-
+    
     // set up the current node for processing
     m_node = r->startNode();
     if (m_node == 0)
         return;
-    m_offset = m_node == startContainer ? startOffset : 0;
+    m_offset = m_node == m_startContainer ? m_startOffset : 0;
     m_handledNode = false;
     m_handledChildren = false;
 
@@ -116,12 +119,10 @@ TextIterator::TextIterator(const Range *r, IteratorKind kind) : m_endContainer(0
     m_textBox = 0;
 
     // initialize record of previous node processing
+    m_haveEmitted = false;
     m_lastTextNode = 0;
     m_lastTextNodeEndedWithCollapsedSpace = false;
-    if (kind == RUNFINDER)
-        m_lastCharacter = 0;
-    else
-        m_lastCharacter = '\n';
+    m_lastCharacter = 0;
 
 #ifndef NDEBUG
     // need this just because of the assert in advance()
@@ -153,19 +154,27 @@ void TextIterator::advance()
             return;
     }
 
-    while (m_node && m_node != m_pastEndNode && !(m_node == m_endContainer && m_endOffset == 0)) {
+    while (m_node && m_node != m_pastEndNode) {
+        // if the range ends at offset 0 of an element, represent the
+        // position, but not the content, of that element e.g. if the
+        // node is a blockflow element, emit a newline that
+        // precedes the element
+        if (m_node == m_endContainer && m_endOffset == 0) {
+            representNodeOffsetZero();
+            m_node = 0;
+            return;
+        }
+        
         // handle current node according to its type
         if (!m_handledNode) {
             RenderObject *renderer = m_node->renderer();
-            if (renderer && renderer->isText() && m_node->nodeType() == Node::TEXT_NODE) {
-                // FIXME: What about CDATA_SECTION_NODE?
-                if (renderer->style()->visibility() == VISIBLE)
-                    m_handledNode = handleTextNode();
-            } else if (renderer && (renderer->isImage() || renderer->isWidget() || (renderer->element() && renderer->element()->isControl()))) {
-                if (renderer->style()->visibility() == VISIBLE)
-                    m_handledNode = handleReplacedElement();
-            } else
+            if (renderer && renderer->isText() && m_node->nodeType() == Node::TEXT_NODE) // FIXME: What about CDATA_SECTION_NODE?
+                m_handledNode = handleTextNode();
+            else if (renderer && (renderer->isImage() || renderer->isWidget() || (renderer->element() && renderer->element()->isControl())))
+                m_handledNode = handleReplacedElement();
+            else
                 m_handledNode = handleNonTextNode();
+
             if (m_positionNode)
                 return;
         }
@@ -211,9 +220,11 @@ static inline bool compareBoxStart(const InlineTextBox *first, const InlineTextB
 
 bool TextIterator::handleTextNode()
 {
-    m_lastTextNode = m_node;
-
     RenderText* renderer = static_cast<RenderText*>(m_node->renderer());
+    if (renderer->style()->visibility() != VISIBLE)
+        return false;
+        
+    m_lastTextNode = m_node;
     String str = renderer->text();
 
     // handle pre-formatted text
@@ -230,15 +241,7 @@ bool TextIterator::handleTextNode()
         if (runStart >= runEnd)
             return true;
 
-        m_positionNode = m_node;
-        m_positionOffsetBaseNode = 0;
-        m_positionStartOffset = runStart;
-        m_positionEndOffset = runEnd;
-        m_textCharacters = str.characters() + runStart;
-        m_textLength = runEnd - runStart;
-
-        m_lastCharacter = str[runEnd - 1];
-
+        emitText(m_node, runStart, runEnd);
         return true;
     }
 
@@ -302,18 +305,9 @@ void TextIterator::handleTextBox()
                 int subrunEnd = str.find('\n', runStart);
                 if (subrunEnd == -1 || subrunEnd > runEnd)
                     subrunEnd = runEnd;
-
+    
                 m_offset = subrunEnd;
-
-                m_positionNode = m_node;
-                m_positionOffsetBaseNode = 0;
-                m_positionStartOffset = runStart;
-                m_positionEndOffset = subrunEnd;
-                m_textCharacters = str.characters() + runStart;
-                m_textLength = subrunEnd - runStart;
-
-                m_lastTextNodeEndedWithCollapsedSpace = false;
-                m_lastCharacter = str[subrunEnd - 1];
+                emitText(m_node, runStart, subrunEnd);
             }
 
             // If we are doing a subrun that doesn't go to the end of the text box,
@@ -339,11 +333,16 @@ void TextIterator::handleTextBox()
 
 bool TextIterator::handleReplacedElement()
 {
+    if (m_node->renderer()->style()->visibility() != VISIBLE)
+        return false;
+
     if (m_lastTextNodeEndedWithCollapsedSpace) {
         emitCharacter(' ', m_lastTextNode->parentNode(), m_lastTextNode, 1, 1);
         return false;
     }
 
+    m_haveEmitted = true;
+    
     m_positionNode = m_node->parentNode();
     m_positionOffsetBaseNode = m_node;
     m_positionStartOffset = 0;
@@ -423,7 +422,7 @@ static bool shouldEmitNewlinesBeforeAndAfterNode(Node* node)
                 || node->hasTagName(trTag)
                 || node->hasTagName(ulTag));
     }
-
+    
     // Need to make an exception for table cells, because they are blocks, but we
     // want them tab-delimited rather than having newlines before and after.
     if (isTableCell(node))
@@ -437,7 +436,6 @@ static bool shouldEmitNewlinesBeforeAndAfterNode(Node* node)
             return true;
     }
     
-    // Check for non-inline block
     return !r->isInline() && r->isRenderBlock() && !r->isBody();
 }
 
@@ -483,25 +481,70 @@ static bool shouldEmitExtraNewlineForNode(Node* node)
     return false;
 }
 
+
+bool TextIterator::shouldRepresentNodeOffsetZero()
+{
+    // Can't represent the position without m_lastTextNode
+    if (!m_lastTextNode)
+        return false;
+    
+    // Leave element positioned flush with start of a paragraph
+    // (e.g. do not insert tab before a table cell at the start of a paragraph)
+    if (m_lastCharacter == '\n')
+        return false;
+    
+    // Otherwise, show the position if we have emitted any characters
+    if (m_haveEmitted)
+        return true;
+    
+    // We've not emitted anything yet. Generally, there is no need for any positioning then.
+    // The only exception is when the element is visually not in the same position as
+    // the start of the range (e.g. the range starts at the end of the previous paragraph).
+    // NOTE: Creating VisiblePositions and comparing them is relatively expensive, so we
+    // make quicker checks to possibly avoid that. Another check that we could make is
+    // is whether the inline vs block flow changed since the previous visible element.
+    // I think we're already in a special enough case that that won't be needed, tho.
+    if (m_node == m_startContainer)
+        return false;
+    
+    if (!m_node->isDescendantOf(m_startContainer))
+        return true;
+    
+    VisiblePosition startPos = VisiblePosition(m_startContainer, m_startOffset, DOWNSTREAM);
+    VisiblePosition currPos = VisiblePosition(m_node, 0, DOWNSTREAM);
+    return startPos != currPos;
+}
+
+void TextIterator::representNodeOffsetZero()
+{
+    // emit a character to show the positioning of m_node
+    if (!shouldRepresentNodeOffsetZero())
+        return;
+    
+    if (shouldEmitTabBeforeNode(m_node))
+        emitCharacter('\t', m_lastTextNode->parentNode(), m_lastTextNode, 0, 1);
+    else if (shouldEmitNewlineBeforeNode(m_node))
+        emitCharacter('\n', m_lastTextNode->parentNode(), m_lastTextNode, 0, 1);
+    else if (shouldEmitSpaceBeforeAndAfterNode(m_node))
+        emitCharacter(' ', m_lastTextNode->parentNode(), m_lastTextNode, 0, 1);
+}
+
 bool TextIterator::handleNonTextNode()
 {
-    if (shouldEmitNewlineForNode(m_node)) {
+    if (shouldEmitNewlineForNode(m_node))
         emitCharacter('\n', m_node->parentNode(), m_node, 0, 1);
-    } else if (m_lastCharacter != '\n' && m_lastTextNode) {
-        // only add the tab or newline if not at the start of a line
-        if (shouldEmitTabBeforeNode(m_node))
-            emitCharacter('\t', m_lastTextNode->parentNode(), m_lastTextNode, 0, 1);
-        else if (shouldEmitNewlineBeforeNode(m_node))
-            emitCharacter('\n', m_lastTextNode->parentNode(), m_lastTextNode, 0, 1);
-        else if (shouldEmitSpaceBeforeAndAfterNode(m_node))
-            emitCharacter(' ', m_lastTextNode->parentNode(), m_lastTextNode, 0, 1);
-    }
+    else
+        representNodeOffsetZero();
 
     return true;
 }
 
 void TextIterator::exitNode()
 {
+    // prevent emitting a newline when exiting a collapsed block at beginning of the range
+    if (!m_haveEmitted)
+        return;
+        
     // Emit with a position *inside* m_node, after m_node's contents, in 
     // case it is a block, because the run should start where the 
     // emitted character is positioned visually.
@@ -527,6 +570,8 @@ void TextIterator::exitNode()
 
 void TextIterator::emitCharacter(UChar c, Node *textNode, Node *offsetBaseNode, int textStartOffset, int textEndOffset)
 {
+    m_haveEmitted = true;
+    
     // remember information with which to construct the TextIterator::range()
     // NOTE: textNode is often not a text node, so the range will specify child nodes of positionNode
     m_positionNode = textNode;
@@ -544,6 +589,23 @@ void TextIterator::emitCharacter(UChar c, Node *textNode, Node *offsetBaseNode,
     m_lastCharacter = c;
 }
 
+void TextIterator::emitText(Node *textNode, int textStartOffset, int textEndOffset)
+{
+    RenderText* renderer = static_cast<RenderText*>(m_node->renderer());
+    String str = renderer->text();
+
+    m_positionNode = textNode;
+    m_positionOffsetBaseNode = 0;
+    m_positionStartOffset = textStartOffset;
+    m_positionEndOffset = textEndOffset;
+    m_textCharacters = str.characters() + textStartOffset;
+    m_textLength = textEndOffset - textStartOffset;
+    m_lastCharacter = str[textEndOffset - 1];
+
+    m_lastTextNodeEndedWithCollapsedSpace = false;
+    m_haveEmitted = true;
+}
+
 PassRefPtr<Range> TextIterator::range() const
 {
     // use the current run information, if we have it
@@ -792,11 +854,10 @@ CharacterIterator::CharacterIterator()
 }
 
 CharacterIterator::CharacterIterator(const Range *r)
-    : m_offset(0), m_runOffset(0), m_atBreak(true), m_textIterator(r, RUNFINDER)
+    : m_offset(0), m_runOffset(0), m_atBreak(true), m_textIterator(r)
 {
-    while (!atEnd() && m_textIterator.length() == 0) {
+    while (!atEnd() && m_textIterator.length() == 0)
         m_textIterator.advance();
-    }
 }
 
 PassRefPtr<Range> CharacterIterator::range() const
@@ -881,7 +942,7 @@ WordAwareIterator::WordAwareIterator()
 }
 
 WordAwareIterator::WordAwareIterator(const Range *r)
-: m_previousText(0), m_didLookAhead(false), m_textIterator(r, RUNFINDER)
+: m_previousText(0), m_didLookAhead(false), m_textIterator(r)
 {
     m_didLookAhead = true;  // so we consider the first chunk from the text iterator
     advance();              // get in position over the first chunk of text
@@ -909,9 +970,8 @@ void WordAwareIterator::advance()
     m_didLookAhead = false;
 
     // Go to next non-empty chunk 
-    while (!m_textIterator.atEnd() && m_textIterator.length() == 0) {
+    while (!m_textIterator.atEnd() && m_textIterator.length() == 0)
         m_textIterator.advance();
-    }
     m_range = m_textIterator.range();
 
     if (m_textIterator.atEnd())
@@ -1038,9 +1098,9 @@ bool CircularSearchBuffer::isMatch() const
 int TextIterator::rangeLength(const Range *r)
 {
     int length = 0;
-    for (TextIterator it(r); !it.atEnd(); it.advance()) {
+    for (TextIterator it(r); !it.atEnd(); it.advance())
         length += it.length();
-    }
+    
     return length;
 }
 
index 030edbc7ce04737dfaddc56c3ccb8b80740aebe2..e94ffe588b152938143b03cd1c4398631ddc646a 100644 (file)
@@ -54,13 +54,11 @@ PassRefPtr<Range> findPlainText(const Range*, const String&, bool forward, bool
 // at points where replaced elements break up the text flow.  The text comes back in
 // chunks so as to optimize for performance of the iteration.
 
-enum IteratorKind { CONTENT = 0, RUNFINDER = 1 };
-
 class TextIterator
 {
 public:
     TextIterator();
-    explicit TextIterator(const Range *, IteratorKind kind = CONTENT );
+    explicit TextIterator(const Range *);
     
     bool atEnd() const { return !m_positionNode; }
     void advance();
@@ -76,11 +74,14 @@ public:
     
 private:
     void exitNode();
+    bool shouldRepresentNodeOffsetZero();
+    void representNodeOffsetZero();
     bool handleTextNode();
     bool handleReplacedElement();
     bool handleNonTextNode();
     void handleTextBox();
     void emitCharacter(UChar, Node *textNode, Node *offsetBaseNode, int textStartOffset, int textEndOffset);
+    void emitText(Node *textNode, int textStartOffset, int textEndOffset);
     
     // Current position, not necessarily of the text being returned, but position
     // as we walk through the DOM tree.
@@ -89,7 +90,9 @@ private:
     bool m_handledNode;
     bool m_handledChildren;
     
-    // End of the range.
+    // The range.
+    Node *m_startContainer;
+    int m_startOffset;
     Node *m_endContainer;
     int m_endOffset;
     Node *m_pastEndNode;
@@ -118,6 +121,9 @@ private:
     // Used when text boxes are out of order (Hebrew/Arabic w/ embeded LTR text)
     Vector<InlineTextBox*> m_sortedTextBoxes;
     size_t m_sortedTextBoxesPosition;
+    
+    // Used when deciding whether to emit a "positioning" (e.g. newline) before any other content
+    bool m_haveEmitted;
 };
 
 // Iterates through the DOM range, returning all the text, and 0-length boundaries
index 1610535fe46dc6c5d5efdcb58efb6ba3b4165f10..34a1b17fcd12fdb63e7c85ecd5bedf0a2a6a92f4 100644 (file)
@@ -142,7 +142,7 @@ static VisiblePosition nextBoundary(const VisiblePosition &c, unsigned (*searchF
     ExceptionCode ec = 0;
     searchRange->selectNodeContents(boundary, ec);
     searchRange->setStart(start.node(), start.offset(), ec);
-    TextIterator it(searchRange.get(), RUNFINDER);
+    TextIterator it(searchRange.get());
     DeprecatedString string;
     unsigned next = 0;
     bool inTextSecurityMode = start.node() && start.node()->renderer() && start.node()->renderer()->style()->textSecurity() != TSNONE;