LayoutTests/ChangeLog

 12011-01-17 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 Add regression test.
 9
 10 * editing/selection/extend-backward-by-word-over-non-editable-expected.txt: Added.
 11 * editing/selection/extend-backward-by-word-over-non-editable.html: Added.
 12
1132011-01-17 Dan Bernstein <mitz@apple.com>
214
315 Reviewed by Darin Adler.

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

 1PASS for LTR
 2PASS for RTL
 3
 4foo bar baz qux quux
 5שוּרה שוּרה שוּרה שוּרה שוּרה

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

 1<!DOCTYPE html>
 2<pre id="log"></pre>
 3
 4<div id="ltrTextContainer" contenteditable>foo bar <span contenteditable=false>baz</span> qux quux</div>
 5<div id="rtlTextContainer" contenteditable dir="rtl">&#x05E9;&#x05D5;&#x05BC;&#x05E8;&#x05D4; &#x05E9;&#x05D5;&#x05BC;&#x05E8;&#x05D4;
 6<span contenteditable=false>&#x05E9;&#x05D5;&#x05BC;&#x05E8;&#x05D4;</span>
 7&#x05E9;&#x05D5;&#x05BC;&#x05E8;&#x05D4; &#x05E9;&#x05D5;&#x05BC;&#x05E8;&#x05D4;</div>
 8
 9<script>
 10function log(s) {
 11 document.getElementById("log").appendChild(document.createTextNode(s + "\n"));
 12}
 13
 14function placeCursorAfterFirstNoneditableChild(container) {
 15 for (var i = 0; i < container.childNodes.length; i++) {
 16 var node = container.childNodes[i];
 17 if (node.isContentEditable !== undefined && !node.isContentEditable) {
 18 getSelection().setPosition(node.nextSibling, 0);
 19 return node;
 20 }
 21 }
 22 throw "Couldn't find noneditable child of " + container.textContent;
 23}
 24
 25ltrTextContainer = document.getElementById("ltrTextContainer");
 26noneditableChild = placeCursorAfterFirstNoneditableChild(ltrTextContainer);
 27getSelection().modify("extend", "backward", "word");
 28
 29if (getSelection().toString() === noneditableChild.textContent)
 30 log("PASS for LTR");
 31else
 32 log("FAIL for LTR, selection is \"" + getSelection() + "\" but should be \"" + noneditableChild.textContent + "\"");
 33
 34rtlTextContainer = document.getElementById("rtlTextContainer");
 35noneditableChild = placeCursorAfterFirstNoneditableChild(rtlTextContainer);
 36getSelection().modify("extend", "backward", "word");
 37
 38if (getSelection().toString() === noneditableChild.textContent)
 39 log("PASS for RTL");
 40else
 41 log("FAIL for RTL, selection is \"" + getSelection() + "\" but should be \"" + noneditableChild.textContent + "\"");
 42
 43log("");
 44
 45if (window.layoutTestController)
 46 layoutTestController.dumpAsText();
 47</script>

Source/WebCore/ChangeLog

 12011-01-17 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: editing/selection/extend-backward-by-word-over-non-editable.html
 9
 10 Revert some previous changes (the TextIteratorEndsAtEditingBoundary text iteration
 11 behaviour) which caused this bug in the first place, and add alternative fix for the problem
 12 that TextIterationEndsAtEditingBoundary solved.
 13
 14 * editing/Editor.cpp:
 15 (WebCore::Editor::markMisspellingsAfterTypingToWord): Use wordStart argument as-is, don't
 16 redundantly call startOfWord on it again.
 17 * editing/TextIterator.cpp: Remove references to TextIteratorEndsAtEditingBoundary, add
 18 alternative fix for the problem which it solved.
 19 (WebCore::TextIterator::TextIterator):
 20 (WebCore::SimplifiedBackwardsTextIterator::SimplifiedBackwardsTextIterator):
 21 (WebCore::SimplifiedBackwardsTextIterator::advance): Call exitNode() until all containers
 22 have been exited, rather than stopping after the first. This fixes the problem where
 23 multiple \n characters were being emitted from the iterator in cases where there should only
 24 have been one, such as nested divs.
 25 * editing/TextIterator.h: Remove RemoveTextIteratorEndsAtEditingBoundary.
 26 * editing/visible_units.cpp:
 27 (WebCore::rightmostEquivalent):
 28 (WebCore::previousBoundary): Use the rightmost equivalent position when returning the
 29 endPosition of the range.
 30
1312011-01-17 Naoki Takano <takano.naoki@gmail.com>
232
333 Reviewed by Kent Tamura.

26292659
26302660 * html/canvas/WebGLRenderingContext.cpp:
26312661
2632 >>>>>>> .r75740
263326622011-01-13 Adrienne Walker <enne@google.com>
26342663
26352664 Reviewed by Kenneth Russell.

Source/WebCore/editing/Editor.cpp

@@void Editor::markMisspellingsAfterTypingToWord(const VisiblePosition &wordStart,
20882088 if (isGrammarCheckingEnabled())
20892089 textCheckingOptions |= MarkGrammar;
20902090
2091  VisibleSelection adjacentWords = VisibleSelection(startOfWord(wordStart, LeftWordIfOnBoundary), endOfWord(wordStart, RightWordIfOnBoundary));
 2091 VisibleSelection adjacentWords = VisibleSelection(wordStart, endOfWord(wordStart, RightWordIfOnBoundary));
20922092 if (textCheckingOptions & MarkGrammar) {
20932093 VisibleSelection selectedSentence = VisibleSelection(startOfSentence(wordStart), endOfSentence(wordStart));
20942094 markAllMisspellingsAndBadGrammarInRanges(textCheckingOptions, adjacentWords.toNormalizedRange().get(), selectedSentence.toNormalizedRange().get());

Source/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()
11281125 if (!next) {
11291126 // Exit empty containers as we pass over them or containers
11301127 // where [container, 0] is where we started iterating.
1131  if (!m_handledNode &&
1132  canHaveChildrenForEditing(m_node) &&
1133  m_node->parentNode() &&
1134  (!m_node->lastChild() || (m_node == m_endNode && m_endOffset == 0))) {
 1128 if (!m_handledNode
 1129 && canHaveChildrenForEditing(m_node)
 1130 && m_node->parentNode()
 1131 && (!m_node->lastChild() || (m_node == m_endNode && !m_endOffset))) {
11351132 exitNode();
11361133 if (m_positionNode) {
11371134 m_handledNode = true;
11381135 m_handledChildren = true;
11391136 return;
1140  }
 1137 }
11411138 }
 1139
11421140 // Exit all other containers.
11431141 while (!m_node->previousSibling()) {
1144  if (!setCurrentNode(m_node->parentOrHostNode()))
 1142 Node* parent = m_node->parentOrHostNode();
 1143 if (!parent)
11451144 break;
 1145 m_node = parent;
11461146 m_fullyClippedStack.pop();
11471147 exitNode();
1148  if (m_positionNode) {
1149  m_handledNode = true;
1150  m_handledChildren = true;
1151  return;
1152  }
 1148 }
 1149 if (m_positionNode) {
 1150 m_handledNode = true;
 1151 m_handledChildren = true;
 1152 return;
11531153 }
11541154
11551155 next = m_node->previousSibling();
11561156 m_fullyClippedStack.pop();
11571157 }
11581158
1159  if (m_node && setCurrentNode(next))
 1159 if (next) {
 1160 m_node = next;
11601161 pushFullyClippedState(m_fullyClippedStack, m_node);
1161  else
 1162 } else
11621163 clearCurrentNode();
11631164
11641165 // For the purpose of word boundary detection,

@@void SimplifiedBackwardsTextIterator::emitCharacter(UChar c, Node* node, int sta
12391240 m_lastCharacter = c;
12401241}
12411242
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)
1248 {
1249  if (!node)
1250  return false;
1251  if (m_behavior == TextIteratorEndsAtEditingBoundary && crossesEditingBoundary(node))
1252  return false;
1253  m_node = node;
1254  return true;
1255 }
1256 
12571243void SimplifiedBackwardsTextIterator::clearCurrentNode()
12581244{
12591245 m_node = 0;

Source/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*);
210207 void clearCurrentNode();
211208
212209 TextIteratorBehavior m_behavior;

Source/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 position)
 53{
 54 Position next = position.next();
 55 while (position.node() != next.node()) {
 56 position = next;
 57 next = position.next();
 58 }
 59 return position;
 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) {
 128 if (it.atEnd() && !next)
119129 pos = it.range()->startPosition();
120  } else if (next != 0) {
 130 else if (next) {
121131 Node *node = it.range()->startContainer(ec);
122  if ((node->isTextNode() && static_cast<int>(next) <= node->maxCharacterOffset()) || (node->renderer() && node->renderer()->isBR() && !next))
 132 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);
125  else {
 135 } 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