LayoutTests/ChangeLog

 12010-12-16 Benjamin Kalman <kalman@chromium.org>
 2
 3 Reviewed by NOBODY (OOPS!).
 4
 5 Moving or selecting backwards by words jumps to start of contenteditable region if contenteditable=false span is encountered
 6 https://bugs.webkit.org/show_bug.cgi?id=51001
 7
 8 Test that moving the selection back by a word over a non-contenteditable span only moves
 9 back a word (as opposed to currently where the selection moves to the start of the parent
 10 contenteditable div).
 11
 12 * editing/selection/extend-backward-by-word-over-non-editable-expected.txt: Added.
 13 * editing/selection/extend-backward-by-word-over-non-editable.html: Added.
 14
1152010-12-14 Brady Eidson <beidson@apple.com>
216
317 Reviewed by Alexey Proskuryakov.

LayoutTests/editing/selection/extend-backward-by-word-over-non-editable-expected.txt

 1Tests that extending backwards over a non-contenteditable word only extends over that word
 2
 3Dump of markup 1:
 4| "foo bar <#selection-focus>"
 5| <span>
 6| contenteditable="false"
 7| "baz"
 8| "<#selection-anchor> qux quux"
 9
 10Dump of markup 2:
 11| "PASS"

LayoutTests/editing/selection/extend-backward-by-word-over-non-editable.html

 1<!DOCTYPE html>
 2<div id="textContainer" contenteditable>foo bar <span contenteditable=false>baz</span> qux quux</div>
 3<div id="result">FAIL</div>
 4
 5<script src="../../resources/dump-as-markup.js"></script>
 6<script>
 7 textContainer = document.getElementById("textContainer");
 8 result = document.getElementById("result");
 9
 10 // Place cursor after "baz".
 11 getSelection().setBaseAndExtent(textContainer.childNodes[2], 0);
 12 // Extend selection backwards by a word; "baz" should be selected.
 13 getSelection().modify("extend", "backward", "word");
 14 if (getSelection().toString() === "baz")
 15 document.getElementById("result").innerHTML = "PASS";
 16
 17 Markup.description("Tests that extending backwards over a non-contenteditable word only extends over that word");
 18 Markup.dump(textContainer);
 19 Markup.dump(result);
 20</script>

WebCore/ChangeLog

 12010-12-16 Benjamin Kalman <kalman@chromium.org>
 2
 3 Reviewed by NOBODY (OOPS!).
 4
 5 Moving or selecting backwards by words jumps to start of contenteditable region if contenteditable=false span is encountered
 6 https://bugs.webkit.org/show_bug.cgi?id=51001
 7
 8 Revert part of changeset 64974 (bugs.webkit.org/b/36359) which caused this regression by
 9 removing references to TextIteratorEndsAtEditingBoundary.
 10 Add an alternative fix which ensures that the rightmost equivalent Position is used to
 11 create the VisiblePosition returned from previousBoundary.
 12
 13 Test: editing/selection/extend-backward-by-word-over-non-editable.html
 14
 15 * editing/TextIterator.cpp:
 16 (WebCore::TextIterator::TextIterator): Remove references to TextIteratorEndsAtEditingBoundary
 17 (WebCore::SimplifiedBackwardsTextIterator::SimplifiedBackwardsTextIterator):
 18 (WebCore::SimplifiedBackwardsTextIterator::advance):
 19 (WebCore::SimplifiedBackwardsTextIterator::setNodeIfNotNull):
 20 * editing/TextIterator.h: Remove TextIteratorEndsAtEditingBoundary
 21 * editing/visible_units.cpp:
 22 (WebCore::rightmostEquivalent):
 23 (WebCore::previousBoundary): Use the rightmost equivalent Position for the returned VisiblePosition
 24
1252010-12-14 Brady Eidson <beidson@apple.com>
226
327 Reviewed by Alexey Proskuryakov.

WebCore/editing/TextIterator.cpp

@@TextIterator::TextIterator(const Range* r, TextIteratorBehavior behavior)
289289 , m_handledFirstLetter(false)
290290 , m_ignoresStyleVisibility(behavior & TextIteratorIgnoresStyleVisibility)
291291{
292  // FIXME: should support TextIteratorEndsAtEditingBoundary http://webkit.org/b/43609
293  ASSERT(behavior != TextIteratorEndsAtEditingBoundary);
294 
295292 if (!r)
296293 return;
297294

@@SimplifiedBackwardsTextIterator::SimplifiedBackwardsTextIterator(const Range* r,
10511048 , m_node(0)
10521049 , m_positionNode(0)
10531050{
1054  ASSERT(m_behavior == TextIteratorDefaultBehavior || m_behavior == TextIteratorEndsAtEditingBoundary);
 1051 ASSERT(m_behavior == TextIteratorDefaultBehavior);
10551052
10561053 if (!r)
10571054 return;

@@SimplifiedBackwardsTextIterator::SimplifiedBackwardsTextIterator(const Range* r,
10761073 }
10771074 }
10781075
1079  setCurrentNode(endNode);
 1076 m_node = endNode;
10801077 setUpFullyClippedStack(m_fullyClippedStack, m_node);
10811078 m_offset = endOffset;
10821079 m_handledNode = false;

@@void SimplifiedBackwardsTextIterator::advance()
11411138 }
11421139 // Exit all other containers.
11431140 while (!m_node->previousSibling()) {
1144  if (!setCurrentNode(m_node->parentOrHostNode()))
 1141 if (!setNodeIfNotNull(m_node->parentOrHostNode()))
11451142 break;
11461143 m_fullyClippedStack.pop();
11471144 exitNode();

@@void SimplifiedBackwardsTextIterator::advance()
11561153 m_fullyClippedStack.pop();
11571154 }
11581155
1159  if (m_node && setCurrentNode(next))
 1156 if (m_node && setNodeIfNotNull(next))
11601157 pushFullyClippedState(m_fullyClippedStack, m_node);
11611158 else
11621159 clearCurrentNode();

@@void SimplifiedBackwardsTextIterator::emitCharacter(UChar c, Node* node, int sta
12391236 m_lastCharacter = c;
12401237}
12411238
1242 bool SimplifiedBackwardsTextIterator::crossesEditingBoundary(Node* node) const
1243 {
1244  return m_node && m_node->isContentEditable() != node->isContentEditable();
1245 }
1246 
1247 bool SimplifiedBackwardsTextIterator::setCurrentNode(Node* node)
 1239bool SimplifiedBackwardsTextIterator::setNodeIfNotNull(Node* node)
12481240{
12491241 if (!node)
12501242 return false;
1251  if (m_behavior == TextIteratorEndsAtEditingBoundary && crossesEditingBoundary(node))
1252  return false;
12531243 m_node = node;
12541244 return true;
12551245}

WebCore/editing/TextIterator.h

@@enum TextIteratorBehavior {
4141 TextIteratorEmitsCharactersBetweenAllVisiblePositions = 1 << 0,
4242 TextIteratorEntersTextControls = 1 << 1,
4343 TextIteratorEmitsTextsWithoutTranscoding = 1 << 2,
44  TextIteratorEndsAtEditingBoundary = 1 << 3,
45  TextIteratorIgnoresStyleVisibility = 1 << 4
 44 TextIteratorIgnoresStyleVisibility = 1 << 3
4645};
47 
 46
4847// FIXME: Can't really answer this question correctly without knowing the white-space mode.
4948// FIXME: Move this somewhere else in the editing directory. It doesn't belong here.
5049inline bool isCollapsibleWhitespace(UChar c)

@@private:
205204 bool handleReplacedElement();
206205 bool handleNonTextNode();
207206 void emitCharacter(UChar, Node*, int startOffset, int endOffset);
208  bool crossesEditingBoundary(Node*) const;
209  bool setCurrentNode(Node*);
 207 bool setNodeIfNotNull(Node*);
210208 void clearCurrentNode();
211209
212210 TextIteratorBehavior m_behavior;

WebCore/editing/visible_units.cpp

@@enum BoundarySearchContextAvailability { DontHaveMoreContext, MayHaveMoreContext
4949
5050typedef unsigned (*BoundarySearchFunction)(const UChar*, unsigned length, unsigned offset, BoundarySearchContextAvailability, bool& needMoreContext);
5151
 52static Position rightmostEquivalent(Position p)
 53{
 54 Position next = p.next();
 55 while (p.node() != next.node()) {
 56 p = next;
 57 next = p.next();
 58 }
 59 return p;
 60}
 61
5262static VisiblePosition previousBoundary(const VisiblePosition& c, BoundarySearchFunction searchFunction)
5363{
5464 Position pos = c.deepEquivalent();

@@static VisiblePosition previousBoundary(const VisiblePosition& c, BoundarySearch
8999 if (ec)
90100 return VisiblePosition();
91101
92  SimplifiedBackwardsTextIterator it(searchRange.get(), TextIteratorEndsAtEditingBoundary);
 102 SimplifiedBackwardsTextIterator it(searchRange.get());
93103 unsigned next = 0;
94104 bool inTextSecurityMode = start.node() && start.node()->renderer() && start.node()->renderer()->style()->textSecurity() != TSNONE;
95105 bool needMoreContext = false;

@@static VisiblePosition previousBoundary(const VisiblePosition& c, BoundarySearch
115125 ASSERT(!needMoreContext);
116126 }
117127
118  if (it.atEnd() && next == 0) {
119  pos = it.range()->startPosition();
120  } else if (next != 0) {
 128 if (it.atEnd() && !next)
 129 pos = it.range().get()->startPosition();
 130 else if (next) {
121131 Node *node = it.range()->startContainer(ec);
122132 if ((node->isTextNode() && static_cast<int>(next) <= node->maxCharacterOffset()) || (node->renderer() && node->renderer()->isBR() && !next))
123133 // The next variable contains a usable index into a text node
124134 pos = Position(node, next);
125135 else {
126136 // Use the character iterator to translate the next value into a DOM position.
127  BackwardsCharacterIterator charIt(searchRange.get(), TextIteratorEndsAtEditingBoundary);
 137 BackwardsCharacterIterator charIt(searchRange.get());
128138 charIt.advance(string.size() - suffixLength - next);
129  pos = charIt.range()->endPosition();
 139 // Take the rightmost equivalent position so that the correct containing editable region is used if a boundary is reached.
 140 pos = rightmostEquivalent(charIt.range()->endPosition());
130141 }
131142 }
132143