Source/WebCore/ChangeLog

 12012-03-15 Xiaomei Ji <xji@chromium.org>
 2
 3 Using ICU break iterator to simplify visual word movement implementation.
 4 https://bugs.webkit.org/show_bug.cgi?id=78856
 5
 6 Reviewed by NOBODY (OOPS!).
 7
 8 This patch relies on ICU word break iterator and cursor visual movement by character to get the word break
 9 position in visual order. It reduces the complexity of old implementation.
 10
 11 Test: editing/selection/move-by-word-visually-wrong-left-right.html
 12
 13 * editing/FrameSelection.cpp: Exclude WinCE from visual word movement since isWordTextBreak is not implemented.
 14 (WebCore::FrameSelection::modifyMovingRight):
 15 (WebCore::FrameSelection::modifyMovingLeft):
 16 * editing/visible_units.cpp:
 17 (WebCore):
 18 (WebCore::previousLeafWithSameEditability): Just moving to the top without functionality change.
 19 (WebCore::enclosingNodeWithNonInlineRenderer): ditto.
 20 (WebCore::nextLeafWithSameEditability): ditto.
 21 (WebCore::previousRootInlineBox): return previous RootInlineBox which is in different renderer.
 22 (WebCore::nextRootInlineBox): return next RootInlineBox which is in different renderer.
 23 (WebCore::boxIndexInVector):
 24 (WebCore::previousBoxInLine): returns logically previous box in one line.
 25 (WebCore::logicallyPreviousBox): returns logically previous box.
 26 (WebCore::nextBoxInLine): returns logically next box in one line.
 27 (WebCore::logicallyNextBox): returns logically next box.
 28 (WebCore::wordBreakIteratorForMinOffsetBoundary): create word break iterator for position that is a box's min offset.
 29 (WebCore::wordBreakIteratorForMaxOffsetBoundary): create word break iterator for position that is a box's max offset.
 30 (WebCore::isLogicalStartOfWord): return whether a position is logically start of word.
 31 (WebCore::islogicalEndOfWord): return whether a position is logically end of word.
 32 (WebCore::visualWordPosition): returns the visual left or right word position.
 33 (WebCore::leftWordPosition):
 34 (WebCore::rightWordPosition):
 35 * platform/text/TextBreakIterator.h: Add isWordTextBreak().
 36 (WebCore):
 37 * platform/text/TextBreakIteratorICU.cpp:
 38 (WebCore::isWordTextBreak):
 39 (WebCore):
 40 * platform/text/gtk/TextBreakIteratorGtk.cpp:
 41 (WebCore::isWordTextBreak):
 42 (WebCore):
 43 * platform/text/qt/TextBreakIteratorQt.cpp:
 44 (WebCore::isWordTextBreak):
 45 (WebCore):
 46 * platform/text/wince/TextBreakIteratorWinCE.cpp:
 47 (WebCore::isWordTextBreak):
 48 (WebCore):
 49
1502012-03-15 Jessie Berlin <jberlin@apple.com>
251
352 Assertion failures in WebCore::Page::goBackOrForward causing multiple "crashes" on Lion Intel
110884

Source/WebCore/editing/FrameSelection.cpp

@@VisiblePosition FrameSelection::modifyMo
642642 pos = VisiblePosition(m_selection.extent(), m_selection.affinity()).right(true);
643643 break;
644644 case WordGranularity:
 645#if !OS(WINCE)
 646 // Visual word movement relies on isWordTextBreak which is not implemented in WinCE.
645647 if (visualWordMovementEnabled()
646648 || (m_frame && m_frame->editor()->behavior().shouldMoveLeftRightByWordInVisualOrder())) {
647649 pos = rightWordPosition(VisiblePosition(m_selection.extent(), m_selection.affinity()));
648650 break;
649651 }
 652#endif
650653 case SentenceGranularity:
651654 case LineGranularity:
652655 case ParagraphGranularity:

@@VisiblePosition FrameSelection::modifyMo
809812 pos = VisiblePosition(m_selection.extent(), m_selection.affinity()).left(true);
810813 break;
811814 case WordGranularity:
 815#if !OS(WINCE)
812816 if (visualWordMovementEnabled()
813817 || (m_frame && m_frame->editor()->behavior().shouldMoveLeftRightByWordInVisualOrder())) {
814818 pos = leftWordPosition(VisiblePosition(m_selection.extent(), m_selection.affinity()));
815819 break;
816820 }
 821#endif
817822 case SentenceGranularity:
818823 case LineGranularity:
819824 case ParagraphGranularity:
110473

Source/WebCore/editing/visible_units.cpp

@@namespace WebCore {
4949using namespace HTMLNames;
5050using namespace WTF::Unicode;
5151
 52static Node* previousLeafWithSameEditability(Node* node, EditableType editableType)
 53{
 54 bool editable = node->rendererIsEditable(editableType);
 55 node = node->previousLeafNode();
 56 while (node) {
 57 if (editable == node->rendererIsEditable(editableType))
 58 return node;
 59 node = node->previousLeafNode();
 60 }
 61 return 0;
 62}
 63
 64static Node* enclosingNodeWithNonInlineRenderer(Node* node)
 65{
 66 for (; node; node = node->parentNode()) {
 67 if (node->renderer() && !node->renderer()->isInline())
 68 return node;
 69 }
 70 return 0;
 71}
 72
 73static Node* nextLeafWithSameEditability(Node* node, int offset)
 74{
 75 bool editable = node->rendererIsEditable();
 76 ASSERT(offset >= 0);
 77 Node* child = node->childNode(offset);
 78 node = child ? child->nextLeafNode() : node->lastDescendant()->nextLeafNode();
 79 while (node) {
 80 if (editable == node->rendererIsEditable())
 81 return node;
 82 node = node->nextLeafNode();
 83 }
 84 return 0;
 85}
 86
 87static Node* nextLeafWithSameEditability(Node* node, EditableType editableType = ContentIsEditable)
 88{
 89 if (!node)
 90 return 0;
 91
 92 bool editable = node->rendererIsEditable(editableType);
 93 node = node->nextLeafNode();
 94 while (node) {
 95 if (editable == node->rendererIsEditable(editableType))
 96 return node;
 97 node = node->nextLeafNode();
 98 }
 99 return 0;
 100}
 101
 102// FIXME: consolidate with code in previousLinePosition.
 103static const RootInlineBox* previousRootInlineBox(const InlineBox* box, const VisiblePosition& visiblePosition)
 104{
 105 Node* highestRoot = highestEditableRoot(visiblePosition.deepEquivalent(), ContentIsEditable);
 106 Node* node = box->renderer()->node();
 107 Node* enclosingBlockNode = enclosingNodeWithNonInlineRenderer(node);
 108 Node* previousNode = previousLeafWithSameEditability(node, ContentIsEditable);
 109
 110 while (previousNode && enclosingBlockNode == enclosingNodeWithNonInlineRenderer(previousNode))
 111 previousNode = previousLeafWithSameEditability(previousNode, ContentIsEditable);
 112
 113 while (previousNode && !previousNode->isShadowRoot()) {
 114 if (highestEditableRoot(firstPositionInOrBeforeNode(previousNode), ContentIsEditable) != highestRoot)
 115 break;
 116
 117 Position pos = previousNode->hasTagName(brTag) ? positionBeforeNode(previousNode) :
 118 createLegacyEditingPosition(previousNode, caretMaxOffset(previousNode));
 119
 120 if (pos.isCandidate()) {
 121 RenderedPosition renderedPos(pos, DOWNSTREAM);
 122 RootInlineBox* root = renderedPos.rootBox();
 123 if (root)
 124 return root;
 125 }
 126
 127 previousNode = previousLeafWithSameEditability(previousNode, ContentIsEditable);
 128 }
 129 return 0;
 130}
 131
 132static const RootInlineBox* nextRootInlineBox(const InlineBox* box, const VisiblePosition& visiblePosition)
 133{
 134 Node* highestRoot = highestEditableRoot(visiblePosition.deepEquivalent(), ContentIsEditable);
 135 Node* node = box->renderer()->node();
 136 Node* enclosingBlockNode = enclosingNodeWithNonInlineRenderer(node);
 137 Node* nextNode = nextLeafWithSameEditability(node, ContentIsEditable);
 138 while (nextNode && enclosingBlockNode == enclosingNodeWithNonInlineRenderer(nextNode))
 139 nextNode = nextLeafWithSameEditability(nextNode, ContentIsEditable);
 140
 141 while (nextNode && !nextNode->isShadowRoot()) {
 142 if (highestEditableRoot(firstPositionInOrBeforeNode(nextNode), ContentIsEditable) != highestRoot)
 143 break;
 144
 145 Position pos;
 146 pos = createLegacyEditingPosition(nextNode, caretMinOffset(nextNode));
 147
 148 if (pos.isCandidate()) {
 149 RenderedPosition renderedPos(pos, DOWNSTREAM);
 150 RootInlineBox* root = renderedPos.rootBox();
 151 if (root)
 152 return root;
 153 }
 154
 155 nextNode = nextLeafWithSameEditability(nextNode, ContentIsEditable);
 156 }
 157 return 0;
 158}
 159
 160static int boxIndexInVector(const InlineTextBox* box, const Vector<InlineBox*>& leafBoxesInLogicalOrder)
 161{
 162 for (size_t i = 0; i < leafBoxesInLogicalOrder.size(); ++i) {
 163 if (box == leafBoxesInLogicalOrder[i])
 164 return i;
 165 }
 166 return 0;
 167}
 168
 169static const InlineTextBox* previousBoxInLine(const RootInlineBox* root, const InlineTextBox* box, Vector<InlineBox*>& leafBoxesInLogicalOrder)
 170{
 171 if (!root)
 172 return 0;
 173
 174 leafBoxesInLogicalOrder.clear();
 175 root->collectLeafBoxesInLogicalOrder(leafBoxesInLogicalOrder);
 176
 177 // If box is null, root is box's previous RootInlineBox, and previousBox is the last logical box in root.
 178 int boxIndex = leafBoxesInLogicalOrder.size() - 1;
 179 if (box)
 180 boxIndex = boxIndexInVector(box, leafBoxesInLogicalOrder) - 1;
 181
 182 for (int i = boxIndex; i >= 0; --i) {
 183 if (leafBoxesInLogicalOrder[i]->isInlineTextBox())
 184 return toInlineTextBox(leafBoxesInLogicalOrder[i]);
 185 }
 186
 187 return 0;
 188}
 189
 190static const InlineTextBox* logicallyPreviousBox(const VisiblePosition& visiblePosition, const InlineTextBox* textBox, bool& previousBoxInDifferentBlock)
 191{
 192 const InlineBox* startBox = textBox;
 193 Vector<InlineBox*> leafBoxesInLogicalOrder;
 194
 195 const InlineTextBox* previousBox = previousBoxInLine(startBox->root(), textBox, leafBoxesInLogicalOrder);
 196 if (previousBox)
 197 return previousBox;
 198
 199 previousBox = previousBoxInLine(startBox->root()->prevRootBox(), 0, leafBoxesInLogicalOrder);
 200 if (previousBox)
 201 return previousBox;
 202
 203 while (1) {
 204 const RootInlineBox* previousRoot = previousRootInlineBox(startBox, visiblePosition);
 205 if (!previousRoot)
 206 break;
 207
 208 previousBox = previousBoxInLine(previousRoot, 0, leafBoxesInLogicalOrder);
 209 if (previousBox) {
 210 previousBoxInDifferentBlock = true;
 211 return previousBox;
 212 }
 213
 214 if (!leafBoxesInLogicalOrder.size())
 215 break;
 216 startBox = leafBoxesInLogicalOrder[0];
 217 }
 218 return 0;
 219}
 220
 221static const InlineTextBox* nextBoxInLine(const RootInlineBox* root, const InlineTextBox* box, Vector<InlineBox*>& leafBoxesInLogicalOrder)
 222{
 223 if (!root)
 224 return 0;
 225
 226 leafBoxesInLogicalOrder.clear();
 227 root->collectLeafBoxesInLogicalOrder(leafBoxesInLogicalOrder);
 228
 229 // If box is null, root is box's next RootInlineBox, and nextBox is the first logical box in root.
 230 // Otherwise, root is box's RootInlineBox, and nextBox is the next logical box in the same line.
 231 size_t nextBoxIndex = 0;
 232 if (box)
 233 nextBoxIndex = boxIndexInVector(box, leafBoxesInLogicalOrder) + 1;
 234
 235 for (size_t i = nextBoxIndex; i < leafBoxesInLogicalOrder.size(); ++i) {
 236 if (leafBoxesInLogicalOrder[i]->isInlineTextBox())
 237 return toInlineTextBox(leafBoxesInLogicalOrder[i]);
 238 }
 239
 240 return 0;
 241}
 242
 243static const InlineTextBox* logicallyNextBox(const VisiblePosition& visiblePosition, const InlineTextBox* textBox, bool& nextBoxInDifferentBlock)
 244{
 245 const InlineBox* startBox = textBox;
 246 Vector<InlineBox*> leafBoxesInLogicalOrder;
 247
 248 const InlineTextBox* nextBox = nextBoxInLine(startBox->root(), textBox, leafBoxesInLogicalOrder);
 249 if (nextBox)
 250 return nextBox;
 251
 252 nextBox = nextBoxInLine(startBox->root()->nextRootBox(), 0, leafBoxesInLogicalOrder);
 253 if (nextBox)
 254 return nextBox;
 255
 256 while (1) {
 257 const RootInlineBox* nextRoot = nextRootInlineBox(startBox, visiblePosition);
 258 if (!nextRoot)
 259 break;
 260
 261 nextBox = nextBoxInLine(nextRoot, 0, leafBoxesInLogicalOrder);
 262 if (nextBox) {
 263 nextBoxInDifferentBlock = true;
 264 return nextBox;
 265 }
 266
 267 if (!leafBoxesInLogicalOrder.size())
 268 break;
 269 startBox = leafBoxesInLogicalOrder[0];
 270 }
 271 return 0;
 272}
 273
 274static TextBreakIterator* wordBreakIteratorForMinOffsetBoundary(const VisiblePosition& visiblePosition, const InlineTextBox* textBox,
 275 int& previousBoxLength, bool& previousBoxInDifferentBlock)
 276{
 277 previousBoxInDifferentBlock = false;
 278
 279 // FIXME: Handle the case when we don't have an inline text box.
 280 const InlineTextBox* previousBox = logicallyPreviousBox(visiblePosition, textBox, previousBoxInDifferentBlock);
 281
 282 int len = 0;
 283 Vector<UChar, 1024> string;
 284 if (previousBox) {
 285 previousBoxLength = previousBox->len();
 286 string.append(previousBox->textRenderer()->text()->characters() + previousBox->start(), previousBoxLength);
 287 len += previousBoxLength;
 288 }
 289 string.append(textBox->textRenderer()->text()->characters() + textBox->start(), textBox->len());
 290 len += textBox->len();
 291
 292 return wordBreakIterator(string.data(), len);
 293}
 294
 295static TextBreakIterator* wordBreakIteratorForMaxOffsetBoundary(const VisiblePosition& visiblePosition, const InlineTextBox* textBox, bool& nextBoxInDifferentBlock)
 296{
 297 nextBoxInDifferentBlock = false;
 298
 299 // FIXME: Handle the case when we don't have an inline text box.
 300 const InlineTextBox* nextBox = logicallyNextBox(visiblePosition, textBox, nextBoxInDifferentBlock);
 301
 302 int len = 0;
 303 Vector<UChar, 1024> string;
 304 string.append(textBox->textRenderer()->text()->characters() + textBox->start(), textBox->len());
 305 len += textBox->len();
 306 if (nextBox) {
 307 string.append(nextBox->textRenderer()->text()->characters() + nextBox->start(), nextBox->len());
 308 len += nextBox->len();
 309 }
 310
 311 return wordBreakIterator(string.data(), len);
 312}
 313
 314static bool isLogicalStartOfWord(TextBreakIterator* iter, int position, bool hardLineBreak)
 315{
 316 bool boundary = hardLineBreak ? true : isTextBreak(iter, position);
 317 if (!boundary)
 318 return false;
 319
 320 textBreakFollowing(iter, position);
 321 // isWordTextBreak returns true after moving across a word and false after moving across a punctuation/space.
 322 return isWordTextBreak(iter);
 323}
 324
 325static bool islogicalEndOfWord(TextBreakIterator* iter, int position, bool hardLineBreak)
 326{
 327 bool boundary = isTextBreak(iter, position);
 328 return (hardLineBreak || boundary) && isWordTextBreak(iter);
 329}
 330
 331enum CursorMovementDirection { MoveLeft, MoveRight };
 332
 333static VisiblePosition visualWordPosition(const VisiblePosition& visiblePosition, CursorMovementDirection direction)
 334{
 335 if (visiblePosition.isNull())
 336 return VisiblePosition();
 337
 338 TextDirection blockDirection = directionOfEnclosingBlock(visiblePosition.deepEquivalent());
 339 InlineBox* previouslyVisitedBox = 0;
 340 VisiblePosition current = visiblePosition;
 341 TextBreakIterator* iter = 0;
 342
 343 while (1) {
 344 VisiblePosition adjacentCharacterPosition = direction == MoveRight ? current.right(true) : current.left(true);
 345 if (adjacentCharacterPosition == current || adjacentCharacterPosition.isNull())
 346 return VisiblePosition();
 347
 348 InlineBox* box;
 349 int offsetInBox;
 350 adjacentCharacterPosition.deepEquivalent().getInlineBoxAndOffset(UPSTREAM, box, offsetInBox);
 351
 352 if (!box)
 353 break;
 354 if (!box->isInlineTextBox()) {
 355 current = adjacentCharacterPosition;
 356 continue;
 357 }
 358
 359 InlineTextBox* textBox = toInlineTextBox(box);
 360 int previousBoxLength = 0;
 361 bool previousBoxInDifferentBlock = false;
 362 bool nextBoxInDifferentBlock = false;
 363 bool movingIntoNewBox = previouslyVisitedBox != box;
 364
 365 if (offsetInBox == box->caretMinOffset())
 366 iter = wordBreakIteratorForMinOffsetBoundary(visiblePosition, textBox, previousBoxLength, previousBoxInDifferentBlock);
 367 else if (offsetInBox == box->caretMaxOffset())
 368 iter = wordBreakIteratorForMaxOffsetBoundary(visiblePosition, textBox, nextBoxInDifferentBlock);
 369 else if (movingIntoNewBox) {
 370 iter = wordBreakIterator(textBox->textRenderer()->text()->characters() + textBox->start(), textBox->len());
 371 previouslyVisitedBox = box;
 372 }
 373
 374 textBreakFirst(iter);
 375 int offsetInIterator = offsetInBox - textBox->start() + previousBoxLength;
 376
 377 bool isWordBreak;
 378 if (box->direction() == blockDirection) {
 379 bool logicalStartInRenderer = offsetInBox == static_cast<int>(textBox->start()) && previousBoxInDifferentBlock;
 380 isWordBreak = isLogicalStartOfWord(iter, offsetInIterator, logicalStartInRenderer);
 381 } else {
 382 bool logicalEndInRenderer = offsetInBox == static_cast<int>(textBox->start() + textBox->len()) && nextBoxInDifferentBlock;
 383 isWordBreak = islogicalEndOfWord(iter, offsetInIterator, logicalEndInRenderer);
 384 }
 385
 386 if (isWordBreak)
 387 return adjacentCharacterPosition;
 388
 389 current = adjacentCharacterPosition;
 390 }
 391 return VisiblePosition();
 392}
 393
 394VisiblePosition leftWordPosition(const VisiblePosition& visiblePosition)
 395{
 396 VisiblePosition leftWordBreak = visualWordPosition(visiblePosition, MoveLeft);
 397 leftWordBreak = visiblePosition.honorEditingBoundaryAtOrBefore(leftWordBreak);
 398
 399 // FIXME: How should we handle a non-editable position?
 400 if (leftWordBreak.isNull() && isEditablePosition(visiblePosition.deepEquivalent())) {
 401 TextDirection blockDirection = directionOfEnclosingBlock(visiblePosition.deepEquivalent());
 402 leftWordBreak = blockDirection == LTR ? startOfEditableContent(visiblePosition) : endOfEditableContent(visiblePosition);
 403 }
 404 return leftWordBreak;
 405}
 406
 407VisiblePosition rightWordPosition(const VisiblePosition& visiblePosition)
 408{
 409 VisiblePosition rightWordBreak = visualWordPosition(visiblePosition, MoveRight);
 410 rightWordBreak = visiblePosition.honorEditingBoundaryAtOrBefore(rightWordBreak);
 411
 412 // FIXME: How should we handle a non-editable position?
 413 if (rightWordBreak.isNull() && isEditablePosition(visiblePosition.deepEquivalent())) {
 414 TextDirection blockDirection = directionOfEnclosingBlock(visiblePosition.deepEquivalent());
 415 rightWordBreak = blockDirection == LTR ? endOfEditableContent(visiblePosition) : startOfEditableContent(visiblePosition);
 416 }
 417 return rightWordBreak;
 418}
 419
 420
52421enum BoundarySearchContextAvailability { DontHaveMoreContext, MayHaveMoreContext };
53422
54423typedef unsigned (*BoundarySearchFunction)(const UChar*, unsigned length, unsigned offset, BoundarySearchContextAvailability, bool& needMoreContext);

@@bool isEndOfLine(const VisiblePosition &
517886 return p.isNotNull() && p == endOfLine(p);
518887}
519888
520 // The first leaf before node that has the same editability as node.
521 static Node* previousLeafWithSameEditability(Node* node, EditableType editableType)
522 {
523  bool editable = node->rendererIsEditable(editableType);
524  Node* n = node->previousLeafNode();
525  while (n) {
526  if (editable == n->rendererIsEditable(editableType))
527  return n;
528  n = n->previousLeafNode();
529  }
530  return 0;
531 }
532 
533 static Node* enclosingNodeWithNonInlineRenderer(Node* n)
534 {
535  for (Node* p = n; p; p = p->parentNode()) {
536  if (p->renderer() && !p->renderer()->isInline())
537  return p;
538  }
539  return 0;
540 }
541 
542889static inline IntPoint absoluteLineDirectionPointToLocalPointInBlock(RootInlineBox* root, int lineDirectionPoint)
543890{
544891 ASSERT(root);

@@VisiblePosition previousLinePosition(con
625972 return VisiblePosition(firstPositionInNode(rootElement), DOWNSTREAM);
626973}
627974
628 static Node* nextLeafWithSameEditability(Node* node, int offset)
629 {
630  bool editable = node->rendererIsEditable();
631  ASSERT(offset >= 0);
632  Node* child = node->childNode(offset);
633  Node* n = child ? child->nextLeafNode() : node->lastDescendant()->nextLeafNode();
634  while (n) {
635  if (editable == n->rendererIsEditable())
636  return n;
637  n = n->nextLeafNode();
638  }
639  return 0;
640 }
641 
642 static Node* nextLeafWithSameEditability(Node* node, EditableType editableType = ContentIsEditable)
643 {
644  if (!node)
645  return 0;
646 
647  bool editable = node->rendererIsEditable(editableType);
648  Node* n = node->nextLeafNode();
649  while (n) {
650  if (editable == n->rendererIsEditable(editableType))
651  return n;
652  n = n->nextLeafNode();
653  }
654  return 0;
655 }
656975
657976VisiblePosition nextLinePosition(const VisiblePosition &visiblePosition, int lineDirectionPoint, EditableType editableType)
658977{

@@VisiblePosition rightBoundaryOfLine(cons
10991418 return direction == LTR ? logicalEndOfLine(c) : logicalStartOfLine(c);
11001419}
11011420
1102 static const int invalidOffset = -1;
1103 static const int offsetNotFound = -1;
1104 
1105 static bool positionIsInBox(const VisiblePosition& wordBreak, const InlineBox* box, int& offsetOfWordBreak)
1106 {
1107  if (wordBreak.isNull())
1108  return false;
1109 
1110  InlineBox* boxOfWordBreak;
1111  wordBreak.getInlineBoxAndOffset(boxOfWordBreak, offsetOfWordBreak);
1112  return box == boxOfWordBreak;
1113 }
1114 
1115 static VisiblePosition previousWordBreakInBoxInsideBlockWithSameDirectionality(const InlineBox* box, const VisiblePosition& previousWordBreak, int& offsetOfWordBreak)
1116 {
1117  // In a LTR block, the word break should be on the left boundary of a word.
1118  // In a RTL block, the word break should be on the right boundary of a word.
1119  // Because nextWordPosition() returns the word break on the right boundary of the word for LTR text,
1120  // we need to use previousWordPosition() to traverse words within the inline boxes from right to left
1121  // to find the previous word break (i.e. the first word break on the left). The same applies to RTL text.
1122 
1123  bool hasSeenWordBreakInThisBox = previousWordBreak.isNotNull();
1124 
1125  VisiblePosition wordBreak;
1126 
1127  if (hasSeenWordBreakInThisBox)
1128  wordBreak = previousWordBreak;
1129  else {
1130  wordBreak = createLegacyEditingPosition(box->renderer()->node(), box->caretMaxOffset());
1131 
1132  // Return the rightmost word boundary of LTR box or leftmost word boundary of RTL box if
1133  // it is not in the previously visited boxes. For example, given a logical text
1134  // "abc def hij opq", there are 2 boxes: the "abc def " (starts at 0 and length is 8)
1135  // and the "hij opq" (starts at 12 and length is 7). The word breaks are
1136  // "abc |def | hij |opq". We normally catch the word break between "def" and "hij" when
1137  // we visit the box that contains "hij opq", but this word break doesn't exist in the box
1138  // that contains "hij opq" when there are multiple spaces. So we detect it when we're
1139  // traversing the box that contains "abc def " instead.
1140 
1141  if ((box->isLeftToRightDirection() && box->nextLeafChild())
1142  || (!box->isLeftToRightDirection() && box->prevLeafChild())) {
1143 
1144  VisiblePosition positionAfterWord = nextBoundary(wordBreak, nextWordPositionBoundary);
1145  if (positionAfterWord.isNotNull()) {
1146  VisiblePosition positionBeforeWord = previousBoundary(positionAfterWord, previousWordPositionBoundary);
1147 
1148  if (positionIsInBox(positionBeforeWord, box, offsetOfWordBreak))
1149  return positionBeforeWord;
1150  }
1151  }
1152  }
1153 
1154  wordBreak = previousBoundary(wordBreak, previousWordPositionBoundary);
1155  if (previousWordBreak == wordBreak)
1156  return VisiblePosition();
1157 
1158  return positionIsInBox(wordBreak, box, offsetOfWordBreak) ? wordBreak : VisiblePosition();
1159 }
1160 
1161 static VisiblePosition leftmostPositionInRTLBoxInLTRBlock(const InlineBox* box)
1162 {
1163  // FIXME: Probably need to take care of bidi level too.
1164  Node* node = box->renderer()->node();
1165  InlineBox* previousLeaf = box->prevLeafChild();
1166  InlineBox* nextLeaf = box->nextLeafChild();
1167 
1168  if (previousLeaf && !previousLeaf->isLeftToRightDirection())
1169  return createLegacyEditingPosition(node, box->caretMaxOffset());
1170 
1171  if (nextLeaf && !nextLeaf->isLeftToRightDirection()) {
1172  if (previousLeaf)
1173  return createLegacyEditingPosition(previousLeaf->renderer()->node(), previousLeaf->caretMaxOffset());
1174 
1175  InlineBox* lastRTLLeaf;
1176  do {
1177  lastRTLLeaf = nextLeaf;
1178  nextLeaf = nextLeaf->nextLeafChild();
1179  } while (nextLeaf && !nextLeaf->isLeftToRightDirection());
1180  return createLegacyEditingPosition(lastRTLLeaf->renderer()->node(), lastRTLLeaf->caretMinOffset());
1181  }
1182 
1183  return createLegacyEditingPosition(node, box->caretMinOffset());
1184 }
1185 
1186 static VisiblePosition rightmostPositionInLTRBoxInRTLBlock(const InlineBox* box)
1187 {
1188  // FIXME: Probably need to take care of bidi level too.
1189  Node* node = box->renderer()->node();
1190  InlineBox* previousLeaf = box->prevLeafChild();
1191  InlineBox* nextLeaf = box->nextLeafChild();
1192 
1193  if (nextLeaf && nextLeaf->isLeftToRightDirection())
1194  return createLegacyEditingPosition(node, box->caretMaxOffset());
1195 
1196  if (previousLeaf && previousLeaf->isLeftToRightDirection()) {
1197  if (nextLeaf)
1198  return createLegacyEditingPosition(nextLeaf->renderer()->node(), nextLeaf->caretMaxOffset());
1199 
1200  InlineBox* firstLTRLeaf;
1201  do {
1202  firstLTRLeaf = previousLeaf;
1203  previousLeaf = previousLeaf->prevLeafChild();
1204  } while (previousLeaf && previousLeaf->isLeftToRightDirection());
1205  return createLegacyEditingPosition(firstLTRLeaf->renderer()->node(), firstLTRLeaf->caretMinOffset());
1206  }
1207 
1208  return createLegacyEditingPosition(node, box->caretMinOffset());
1209 }
1210 
1211 static VisiblePosition lastWordBreakInBox(const InlineBox* box, int& offsetOfWordBreak)
1212 {
1213  // Add the leftmost word break for RTL box or rightmost word break for LTR box.
1214  InlineBox* previousLeaf = box->prevLeafChild();
1215  InlineBox* nextLeaf = box->nextLeafChild();
1216  VisiblePosition boundaryPosition;
1217  if (box->direction() == RTL && (!previousLeaf || previousLeaf->isLeftToRightDirection()))
1218  boundaryPosition = leftmostPositionInRTLBoxInLTRBlock(box);
1219  else if (box->direction() == LTR && (!nextLeaf || !nextLeaf->isLeftToRightDirection()))
1220  boundaryPosition = rightmostPositionInLTRBoxInRTLBlock(box);
1221 
1222  if (boundaryPosition.isNull())
1223  return VisiblePosition();
1224 
1225  VisiblePosition wordBreak = nextBoundary(boundaryPosition, nextWordPositionBoundary);
1226  if (wordBreak.isNull())
1227  wordBreak = boundaryPosition;
1228  else if (wordBreak != boundaryPosition)
1229  wordBreak = previousBoundary(wordBreak, previousWordPositionBoundary);
1230 
1231  return positionIsInBox(wordBreak, box, offsetOfWordBreak) ? wordBreak : VisiblePosition();
1232 }
1233 
1234 static bool positionIsVisuallyOrderedInBoxInBlockWithDifferentDirectionality(const VisiblePosition& wordBreak, const InlineBox* box, int& offsetOfWordBreak)
1235 {
1236  int previousOffset = offsetOfWordBreak;
1237  return positionIsInBox(wordBreak, box, offsetOfWordBreak)
1238  && (previousOffset == invalidOffset || previousOffset < offsetOfWordBreak);
1239 }
1240 
1241 static VisiblePosition nextWordBreakInBoxInsideBlockWithDifferentDirectionality(
1242  const InlineBox* box, const VisiblePosition& previousWordBreak, int& offsetOfWordBreak, bool& isLastWordBreakInBox)
1243 {
1244  // FIXME: Probably need to take care of bidi level too.
1245 
1246  // In a LTR block, the word break should be on the left boundary of a word.
1247  // In a RTL block, the word break should be on the right boundary of a word.
1248  // Because previousWordPosition() returns the word break on the right boundary of the word for RTL text,
1249  // we need to use nextWordPosition() to traverse words within the inline boxes from right to left to find the next word break.
1250  // The same applies to LTR text, in which words are traversed within the inline boxes from left to right.
1251 
1252  bool hasSeenWordBreakInThisBox = previousWordBreak.isNotNull();
1253  VisiblePosition wordBreak = hasSeenWordBreakInThisBox ? previousWordBreak :
1254  createLegacyEditingPosition(box->renderer()->node(), box->caretMinOffset());
1255 
1256  wordBreak = nextBoundary(wordBreak, nextWordPositionBoundary);
1257 
1258  // Given RTL box "ABC DEF" either follows a LTR box or is the first visual box in an LTR block as an example,
1259  // 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)",
1260  // where the number in parenthesis represents offset in visiblePosition.
1261  // 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.
1262  // But nextWordPosition() of offset 7 is offset 11, which should be ignored,
1263  // and the position at offset 0 should be manually added as the last word break within the box.
1264  if (wordBreak != previousWordBreak && positionIsVisuallyOrderedInBoxInBlockWithDifferentDirectionality(wordBreak, box, offsetOfWordBreak)) {
1265  isLastWordBreakInBox = false;
1266  return wordBreak;
1267  }
1268 
1269  isLastWordBreakInBox = true;
1270  return lastWordBreakInBox(box, offsetOfWordBreak);
1271 }
1272 
1273 struct WordBoundaryEntry {
1274  WordBoundaryEntry()
1275  : offsetInInlineBox(invalidOffset)
1276  {
1277  }
1278 
1279  WordBoundaryEntry(const VisiblePosition& position, int offset)
1280  : visiblePosition(position)
1281  , offsetInInlineBox(offset)
1282  {
1283  }
1284 
1285  VisiblePosition visiblePosition;
1286  int offsetInInlineBox;
1287 };
1288 
1289 typedef Vector<WordBoundaryEntry, 50> WordBoundaryVector;
1290 
1291 static void collectWordBreaksInBoxInsideBlockWithSameDirectionality(const InlineBox* box, WordBoundaryVector& orderedWordBoundaries)
1292 {
1293  orderedWordBoundaries.clear();
1294 
1295  VisiblePosition wordBreak;
1296  int offsetOfWordBreak = invalidOffset;
1297  while (1) {
1298  wordBreak = previousWordBreakInBoxInsideBlockWithSameDirectionality(box, wordBreak, offsetOfWordBreak);
1299  if (wordBreak.isNull())
1300  break;
1301  WordBoundaryEntry wordBoundaryEntry(wordBreak, offsetOfWordBreak);
1302  orderedWordBoundaries.append(wordBoundaryEntry);
1303  }
1304 }
1305 
1306 static void collectWordBreaksInBoxInsideBlockWithDifferntDirectionality(const InlineBox* box, WordBoundaryVector& orderedWordBoundaries)
1307 {
1308  orderedWordBoundaries.clear();
1309 
1310  VisiblePosition wordBreak;
1311  int offsetOfWordBreak = invalidOffset;
1312  bool isLastWordBreakInBox = false;
1313  while (1) {
1314  wordBreak = nextWordBreakInBoxInsideBlockWithDifferentDirectionality(box, wordBreak, offsetOfWordBreak, isLastWordBreakInBox);
1315  if (wordBreak.isNotNull()) {
1316  WordBoundaryEntry wordBoundaryEntry(wordBreak, offsetOfWordBreak);
1317  orderedWordBoundaries.append(wordBoundaryEntry);
1318  }
1319  if (isLastWordBreakInBox)
1320  break;
1321  }
1322 }
1323 
1324 static void collectWordBreaksInBox(const InlineBox* box, WordBoundaryVector& orderedWordBoundaries, TextDirection blockDirection)
1325 {
1326  if (box->direction() == blockDirection)
1327  collectWordBreaksInBoxInsideBlockWithSameDirectionality(box, orderedWordBoundaries);
1328  else
1329  collectWordBreaksInBoxInsideBlockWithDifferntDirectionality(box, orderedWordBoundaries);
1330 }
1331 
1332 static VisiblePosition previousWordBoundaryInBox(const InlineBox* box, int offset)
1333 {
1334  int offsetOfWordBreak = 0;
1335  VisiblePosition wordBreak;
1336  while (true) {
1337  wordBreak = previousWordBreakInBoxInsideBlockWithSameDirectionality(box, wordBreak, offsetOfWordBreak);
1338  if (wordBreak.isNull())
1339  break;
1340  if (offset == invalidOffset || offsetOfWordBreak != offset)
1341  return wordBreak;
1342  }
1343  return VisiblePosition();
1344 }
1345 
1346 static VisiblePosition nextWordBoundaryInBox(const InlineBox* box, int offset)
1347 {
1348  int offsetOfWordBreak = 0;
1349  VisiblePosition wordBreak;
1350  bool isLastWordBreakInBox = false;
1351  do {
1352  wordBreak = nextWordBreakInBoxInsideBlockWithDifferentDirectionality(box, wordBreak, offsetOfWordBreak, isLastWordBreakInBox);
1353  if (wordBreak.isNotNull() && (offset == invalidOffset || offsetOfWordBreak != offset))
1354  return wordBreak;
1355  } while (!isLastWordBreakInBox);
1356  return VisiblePosition();
1357 }
1358 
1359 static VisiblePosition visuallyLastWordBoundaryInBox(const InlineBox* box, int offset, TextDirection blockDirection)
1360 {
1361  WordBoundaryVector orderedWordBoundaries;
1362  collectWordBreaksInBox(box, orderedWordBoundaries, blockDirection);
1363  if (!orderedWordBoundaries.size())
1364  return VisiblePosition();
1365  if (offset == invalidOffset || orderedWordBoundaries[orderedWordBoundaries.size() - 1].offsetInInlineBox != offset)
1366  return orderedWordBoundaries[orderedWordBoundaries.size() - 1].visiblePosition;
1367  if (orderedWordBoundaries.size() > 1)
1368  return orderedWordBoundaries[orderedWordBoundaries.size() - 2].visiblePosition;
1369  return VisiblePosition();
1370 }
1371 
1372 static int greatestOffsetUnder(int offset, bool boxAndBlockAreInSameDirection, const WordBoundaryVector& orderedWordBoundaries)
1373 {
1374  if (!orderedWordBoundaries.size())
1375  return offsetNotFound;
1376  // FIXME: binary search.
1377  if (boxAndBlockAreInSameDirection) {
1378  for (unsigned i = 0; i < orderedWordBoundaries.size(); ++i) {
1379  if (orderedWordBoundaries[i].offsetInInlineBox < offset)
1380  return i;
1381  }
1382  return offsetNotFound;
1383  }
1384  for (int i = orderedWordBoundaries.size() - 1; i >= 0; --i) {
1385  if (orderedWordBoundaries[i].offsetInInlineBox < offset)
1386  return i;
1387  }
1388  return offsetNotFound;
1389 }
1390 
1391 static int smallestOffsetAbove(int offset, bool boxAndBlockAreInSameDirection, const WordBoundaryVector& orderedWordBoundaries)
1392 {
1393  if (!orderedWordBoundaries.size())
1394  return offsetNotFound;
1395  // FIXME: binary search.
1396  if (boxAndBlockAreInSameDirection) {
1397  for (int i = orderedWordBoundaries.size() - 1; i >= 0; --i) {
1398  if (orderedWordBoundaries[i].offsetInInlineBox > offset)
1399  return i;
1400  }
1401  return offsetNotFound;
1402  }
1403  for (unsigned i = 0; i < orderedWordBoundaries.size(); ++i) {
1404  if (orderedWordBoundaries[i].offsetInInlineBox > offset)
1405  return i;
1406  }
1407  return offsetNotFound;
1408 }
1409 
1410 static const RootInlineBox* previousRootInlineBox(const InlineBox* box)
1411 {
1412  Node* node = box->renderer()->node();
1413  Node* enclosingBlockNode = enclosingNodeWithNonInlineRenderer(node);
1414  Node* previousNode = node->previousLeafNode();
1415  while (previousNode && enclosingBlockNode == enclosingNodeWithNonInlineRenderer(previousNode))
1416  previousNode = previousNode->previousLeafNode();
1417 
1418  while (previousNode && !previousNode->isShadowRoot()) {
1419  Position pos = createLegacyEditingPosition(previousNode, caretMaxOffset(previousNode));
1420 
1421  if (pos.isCandidate()) {
1422  RenderedPosition renderedPos(pos, DOWNSTREAM);
1423  RootInlineBox* root = renderedPos.rootBox();
1424  if (root)
1425  return root;
1426  }
1427 
1428  previousNode = previousNode->previousLeafNode();
1429  }
1430  return 0;
1431 }
1432 
1433 static const RootInlineBox* nextRootInlineBox(const InlineBox* box)
1434 {
1435  Node* node = box->renderer()->node();
1436  Node* enclosingBlockNode = enclosingNodeWithNonInlineRenderer(node);
1437  Node* nextNode = node->nextLeafNode();
1438  while (nextNode && enclosingBlockNode == enclosingNodeWithNonInlineRenderer(nextNode))
1439  nextNode = nextNode->nextLeafNode();
1440 
1441  while (nextNode && !nextNode->isShadowRoot()) {
1442  Position pos;
1443  pos = createLegacyEditingPosition(nextNode, caretMinOffset(nextNode));
1444 
1445  if (pos.isCandidate()) {
1446  RenderedPosition renderedPos(pos, DOWNSTREAM);
1447  RootInlineBox* root = renderedPos.rootBox();
1448  if (root)
1449  return root;
1450  }
1451 
1452  nextNode = nextNode->nextLeafNode();
1453  }
1454  return 0;
1455 }
1456 
1457 static const InlineBox* leftInlineBox(const InlineBox* box, TextDirection blockDirection)
1458 {
1459  if (box->prevLeafChild())
1460  return box->prevLeafChild();
1461 
1462  const RootInlineBox* rootBox = box->root();
1463  const bool isBlockLTR = blockDirection == LTR;
1464  const InlineFlowBox* leftLineBox = isBlockLTR ? rootBox->prevLineBox() : rootBox->nextLineBox();
1465  if (leftLineBox)
1466  return leftLineBox->lastLeafChild();
1467 
1468  const RootInlineBox* leftRootInlineBox = isBlockLTR ? previousRootInlineBox(box) :
1469  nextRootInlineBox(box);
1470  return leftRootInlineBox ? leftRootInlineBox->lastLeafChild() : 0;
1471 }
1472 
1473 static const InlineBox* rightInlineBox(const InlineBox* box, TextDirection blockDirection)
1474 {
1475  if (box->nextLeafChild())
1476  return box->nextLeafChild();
1477 
1478  const RootInlineBox* rootBox = box->root();
1479  const bool isBlockLTR = blockDirection == LTR;
1480  const InlineFlowBox* rightLineBox = isBlockLTR ? rootBox->nextLineBox() : rootBox->prevLineBox();
1481  if (rightLineBox)
1482  return rightLineBox->firstLeafChild();
1483 
1484  const RootInlineBox* rightRootInlineBox = isBlockLTR ? nextRootInlineBox(box) :
1485  previousRootInlineBox(box);
1486  return rightRootInlineBox ? rightRootInlineBox->firstLeafChild() : 0;
1487 }
1488 
1489 static VisiblePosition leftWordBoundary(const InlineBox* box, int offset, TextDirection blockDirection)
1490 {
1491  VisiblePosition wordBreak;
1492  for (const InlineBox* adjacentBox = box; adjacentBox; adjacentBox = leftInlineBox(adjacentBox, blockDirection)) {
1493  if (blockDirection == LTR) {
1494  if (adjacentBox->isLeftToRightDirection())
1495  wordBreak = previousWordBoundaryInBox(adjacentBox, adjacentBox == box ? offset : invalidOffset);
1496  else
1497  wordBreak = nextWordBoundaryInBox(adjacentBox, adjacentBox == box ? offset : invalidOffset);
1498  } else
1499  wordBreak = visuallyLastWordBoundaryInBox(adjacentBox, adjacentBox == box ? offset : invalidOffset, blockDirection);
1500  if (wordBreak.isNotNull())
1501  return wordBreak;
1502  }
1503  return VisiblePosition();
1504 }
1505 
1506 static VisiblePosition rightWordBoundary(const InlineBox* box, int offset, TextDirection blockDirection)
1507 {
1508 
1509  VisiblePosition wordBreak;
1510  for (const InlineBox* adjacentBox = box; adjacentBox; adjacentBox = rightInlineBox(adjacentBox, blockDirection)) {
1511  if (blockDirection == RTL) {
1512  if (adjacentBox->isLeftToRightDirection())
1513  wordBreak = nextWordBoundaryInBox(adjacentBox, adjacentBox == box ? offset : invalidOffset);
1514  else
1515  wordBreak = previousWordBoundaryInBox(adjacentBox, adjacentBox == box ? offset : invalidOffset);
1516  } else
1517  wordBreak = visuallyLastWordBoundaryInBox(adjacentBox, adjacentBox == box ? offset : invalidOffset, blockDirection);
1518  if (!wordBreak.isNull())
1519  return wordBreak;
1520  }
1521  return VisiblePosition();
1522 }
1523 
1524 static bool positionIsInBoxButNotOnBoundary(const VisiblePosition& wordBreak, const InlineBox* box)
1525 {
1526  int offsetOfWordBreak;
1527  return positionIsInBox(wordBreak, box, offsetOfWordBreak)
1528  && offsetOfWordBreak != box->caretMaxOffset() && offsetOfWordBreak != box->caretMinOffset();
1529 }
1530 
1531 static VisiblePosition leftWordPositionIgnoringEditingBoundary(const VisiblePosition& visiblePosition)
1532 {
1533  InlineBox* box;
1534  int offset;
1535  visiblePosition.getInlineBoxAndOffset(box, offset);
1536 
1537  if (!box)
1538  return VisiblePosition();
1539 
1540  TextDirection blockDirection = directionOfEnclosingBlock(visiblePosition.deepEquivalent());
1541 
1542  // FIXME: If the box's directionality is the same as that of the enclosing block, when the offset is at the box boundary
1543  // and the direction is towards inside the box, do I still need to make it a special case? For example, a LTR box inside a LTR block,
1544  // when offset is at box's caretMinOffset and the direction is DirectionRight, should it be taken care as a general case?
1545  if (offset == box->caretLeftmostOffset())
1546  return leftWordBoundary(leftInlineBox(box, blockDirection), invalidOffset, blockDirection);
1547  if (offset == box->caretRightmostOffset())
1548  return leftWordBoundary(box, offset, blockDirection);
1549 
1550 
1551  VisiblePosition wordBreak;
1552  if (blockDirection == LTR) {
1553  if (box->direction() == blockDirection)
1554  wordBreak = previousBoundary(visiblePosition, previousWordPositionBoundary);
1555  else
1556  wordBreak = nextBoundary(visiblePosition, nextWordPositionBoundary);
1557  }
1558  if (wordBreak.isNotNull() && positionIsInBoxButNotOnBoundary(wordBreak, box))
1559  return wordBreak;
1560 
1561  WordBoundaryVector orderedWordBoundaries;
1562  collectWordBreaksInBox(box, orderedWordBoundaries, blockDirection);
1563 
1564  int index = box->isLeftToRightDirection() ? greatestOffsetUnder(offset, blockDirection == LTR, orderedWordBoundaries)
1565  : smallestOffsetAbove(offset, blockDirection == RTL, orderedWordBoundaries);
1566  if (index >= 0)
1567  return orderedWordBoundaries[index].visiblePosition;
1568 
1569  return leftWordBoundary(leftInlineBox(box, blockDirection), invalidOffset, blockDirection);
1570 }
1571 
1572 static VisiblePosition rightWordPositionIgnoringEditingBoundary(const VisiblePosition& visiblePosition)
1573 {
1574  InlineBox* box;
1575  int offset;
1576  visiblePosition.getInlineBoxAndOffset(box, offset);
1577 
1578  if (!box)
1579  return VisiblePosition();
1580 
1581  TextDirection blockDirection = directionOfEnclosingBlock(visiblePosition.deepEquivalent());
1582 
1583  if (offset == box->caretLeftmostOffset())
1584  return rightWordBoundary(box, offset, blockDirection);
1585  if (offset == box->caretRightmostOffset())
1586  return rightWordBoundary(rightInlineBox(box, blockDirection), invalidOffset, blockDirection);
1587 
1588  VisiblePosition wordBreak;
1589  if (blockDirection == RTL) {
1590  if (box->direction() == blockDirection)
1591  wordBreak = previousBoundary(visiblePosition, previousWordPositionBoundary);
1592  else
1593  wordBreak = nextBoundary(visiblePosition, nextWordPositionBoundary);
1594  }
1595  if (wordBreak.isNotNull() && positionIsInBoxButNotOnBoundary(wordBreak, box))
1596  return wordBreak;
1597 
1598  WordBoundaryVector orderedWordBoundaries;
1599  collectWordBreaksInBox(box, orderedWordBoundaries, blockDirection);
1600 
1601  int index = box->isLeftToRightDirection() ? smallestOffsetAbove(offset, blockDirection == LTR, orderedWordBoundaries)
1602  : greatestOffsetUnder(offset, blockDirection == RTL, orderedWordBoundaries);
1603  if (index >= 0)
1604  return orderedWordBoundaries[index].visiblePosition;
1605 
1606  return rightWordBoundary(rightInlineBox(box, blockDirection), invalidOffset, blockDirection);
1607 }
1608 
1609 VisiblePosition leftWordPosition(const VisiblePosition& visiblePosition)
1610 {
1611  if (visiblePosition.isNull())
1612  return VisiblePosition();
1613 
1614  VisiblePosition leftWordBreak = leftWordPositionIgnoringEditingBoundary(visiblePosition);
1615  leftWordBreak = visiblePosition.honorEditingBoundaryAtOrBefore(leftWordBreak);
1616 
1617  // FIXME: How should we handle a non-editable position?
1618  if (leftWordBreak.isNull() && isEditablePosition(visiblePosition.deepEquivalent())) {
1619  TextDirection blockDirection = directionOfEnclosingBlock(visiblePosition.deepEquivalent());
1620  leftWordBreak = blockDirection == LTR ? startOfEditableContent(visiblePosition) : endOfEditableContent(visiblePosition);
1621  }
1622  return leftWordBreak;
1623 }
1624 
1625 VisiblePosition rightWordPosition(const VisiblePosition& visiblePosition)
1626 {
1627  if (visiblePosition.isNull())
1628  return VisiblePosition();
1629 
1630  VisiblePosition rightWordBreak = rightWordPositionIgnoringEditingBoundary(visiblePosition);
1631  rightWordBreak = visiblePosition.honorEditingBoundaryAtOrBefore(rightWordBreak);
1632 
1633  // FIXME: How should we handle a non-editable position?
1634  if (rightWordBreak.isNull() && isEditablePosition(visiblePosition.deepEquivalent())) {
1635  TextDirection blockDirection = directionOfEnclosingBlock(visiblePosition.deepEquivalent());
1636  rightWordBreak = blockDirection == LTR ? endOfEditableContent(visiblePosition) : startOfEditableContent(visiblePosition);
1637  }
1638  return rightWordBreak;
1639 }
1640 
16411421}
110473

Source/WebCore/platform/text/TextBreakIterator.h

@@namespace WebCore {
5656 int textBreakPreceding(TextBreakIterator*, int);
5757 int textBreakFollowing(TextBreakIterator*, int);
5858 bool isTextBreak(TextBreakIterator*, int);
 59 bool isWordTextBreak(TextBreakIterator*);
5960
6061 const int TextBreakDone = -1;
6162
110473

Source/WebCore/platform/text/TextBreakIteratorICU.cpp

@@bool isTextBreak(TextBreakIterator* iter
139139 return ubrk_isBoundary(reinterpret_cast<UBreakIterator*>(iterator), position);
140140}
141141
 142bool isWordTextBreak(TextBreakIterator* iterator)
 143{
 144 int ruleStatus = ubrk_getRuleStatus(reinterpret_cast<UBreakIterator*>(iterator));
 145 return ruleStatus != UBRK_WORD_NONE;
 146}
 147
142148static TextBreakIterator* setUpIteratorWithRules(bool& createdIterator, TextBreakIterator*& iterator,
143149 const char* breakRules, const UChar* string, int length)
144150{
110473

Source/WebCore/platform/text/gtk/TextBreakIteratorGtk.cpp

@@bool isTextBreak(TextBreakIterator* iter
385385 return iterator->m_charIterator.getIndex() == index;
386386}
387387
 388bool isWordTextBreak(TextBreakIterator*)
 389{
 390 return true;
 391}
 392
388393}
110473

Source/WebCore/platform/text/qt/TextBreakIteratorQt.cpp

@@namespace WebCore {
150150 return true;
151151 }
152152
 153 bool isWordTextBreak(TextBreakIterator*)
 154 {
 155 return true;
 156 }
 157
153158}
110473

Source/WebCore/platform/text/wince/TextBreakIteratorWinCE.cpp

@@bool isTextBreak(TextBreakIterator*, int
317317 return true;
318318}
319319
 320bool isWordTextBreak(TextBreakIterator*)
 321{
 322 return true;
 323}
 324
320325TextBreakIterator* cursorMovementIterator(const UChar* string, int length)
321326{
322327 return characterBreakIterator(string, length);
110473

LayoutTests/ChangeLog

 12012-03-15 Xiaomei Ji <xji@chromium.org>
 2
 3 Using ICU break iterator to simplify visual word movement implementation.
 4 https://bugs.webkit.org/show_bug.cgi?id=78856
 5
 6 Reviewed by NOBODY (OOPS!).
 7
 8 * editing/selection/move-by-word-visually-inline-block-positioned-element-expected.txt:
 9 * editing/selection/move-by-word-visually-inline-block-positioned-element.html:
 10 The word break stops at the beginning of fixed positioned element, which is correct and consistent behavior.
 11
 12 * editing/selection/move-by-word-visually-multi-line-expected.txt:
 13 * editing/selection/move-by-word-visually-multi-line.html:
 14 The word break does not stop at the end of a text if there is next line of text available under the same
 15 editable root, which is a consistent behavior cross the board.
 16 Add more test cases, including one with non InlineTextBox.
 17
 18 * editing/selection/move-by-word-visually-single-space-inline-element-expected.txt:
 19 Fixing of previously missing or extra word break positions.
 20
 21 * editing/selection/move-by-word-visually-single-space-one-element-expected.txt:
 22 * editing/selection/move-by-word-visually-single-space-one-element.html:
 23 Add a test case including bidi control character which renders wrong result since right-arrow could not reach
 24 a position which is the word break position.
 25 Add another test case containing non InlineTextBox.
 26
 27 * editing/selection/move-by-word-visually-wrong-left-right-expected.txt: Added.
 28 * editing/selection/move-by-word-visually-wrong-left-right.html: Added.
 29 Add a test case which renders wrong result due to left/right-arrow returns wrong result.
 30
 31 * editing/selection/resources/move-by-word-visually.js:
 32 (moveByWordOnEveryChar): Handle a special case when left/right arrow missing certain position.
 33
1342012-03-15 Brady Eidson <beidson@apple.com>
235
336 <rdar://problem/11036900> and https://bugs.webkit.org/show_bug.cgi?id=81079
110884

LayoutTests/editing/selection/move-by-word-visually-inline-block-positioned-element-expected.txt

22======== Move By Word ====
33Test 1, LTR:
44Move right by one word
5 "begin start"[0, 6], "abc def"[0, 4], "end ing"[0, 4], "this is float"[0, 5, 8], "this is fixed"[5, 8], "this is relative"[0, 5, 8], "this is absolute"[0, 5, 8, 16]
 5"begin start"[0, 6], "abc def"[0, 4], "end ing"[0, 4], "this is float"[0, 5, 8], "this is fixed"[0, 5, 8], "this is relative"[0, 5, 8], "this is absolute"[0, 5, 8, 16]
66Move left by one word
7 "this is absolute"[16, 8, 5, 0], "this is relative"[8, 5, 0], "this is fixed"[8, 5], "this is float"[8, 5, 0], "end ing"[4, 0], "abc def"[4, 0], "begin start"[6, 0]
 7"this is absolute"[16, 8, 5, 0], "this is relative"[8, 5, 0], "this is fixed"[8, 5, 0], "this is float"[8, 5, 0], "end ing"[4, 0], "abc def"[4, 0], "begin start"[6, 0]
88Test 2, LTR:
99Move right by one word
10 "abc def"[0, 4], "end ing"[0, 4], "this is float"[0, 5, 8], "this is fixed"[5, 8], "this is relative"[0, 5, 8], "this is absolute"[0, 5, 8, 16]
 10"abc def"[0, 4], "end ing"[0, 4], "this is float"[0, 5, 8], "this is fixed"[0, 5, 8], "this is relative"[0, 5, 8], "this is absolute"[0, 5, 8, 16]
1111Move left by one word
12 "this is absolute"[16, 8, 5, 0], "this is relative"[8, 5, 0], "this is fixed"[8, 5], "this is float"[8, 5, 0], "end ing"[4, 0], "abc def"[4, 0], "begin start"[6, 0]
 12"this is absolute"[16, 8, 5, 0], "this is relative"[8, 5, 0], "this is fixed"[8, 5, 0], "this is float"[8, 5, 0], "end ing"[4, 0], "abc def"[4, 0], "begin start"[6, 0]
1313Test 3, LTR:
1414Move right by one word
15 "end ing"[0, 4], "this is float"[0, 5, 8], "this is fixed"[5, 8], "this is relative"[0, 5, 8], "this is absolute"[0, 5, 8, 16]
 15"end ing"[0, 4], "this is float"[0, 5, 8], "this is fixed"[0, 5, 8], "this is relative"[0, 5, 8], "this is absolute"[0, 5, 8, 16]
1616Move left by one word
17 "this is absolute"[16, 8, 5, 0], "this is relative"[8, 5, 0], "this is fixed"[8, 5], "this is float"[8, 5, 0], "end ing"[4, 0], "abc def"[4, 0], "begin start"[6, 0]
 17"this is absolute"[16, 8, 5, 0], "this is relative"[8, 5, 0], "this is fixed"[8, 5, 0], "this is float"[8, 5, 0], "end ing"[4, 0], "abc def"[4, 0], "begin start"[6, 0]
1818Test 4, LTR:
1919Move right by one word
20 "this is float"[0, 5, 8], "this is fixed"[5, 8], "this is relative"[0, 5, 8], "this is absolute"[0, 5, 8, 16]
 20"this is float"[0, 5, 8], "this is fixed"[0, 5, 8], "this is relative"[0, 5, 8], "this is absolute"[0, 5, 8, 16]
2121Move left by one word
22 "this is absolute"[16, 8, 5, 0], "this is relative"[8, 5, 0], "this is fixed"[8, 5], "this is float"[8, 5, 0], "end ing"[4, 0], "abc def"[4, 0], "begin start"[6, 0]
 22"this is absolute"[16, 8, 5, 0], "this is relative"[8, 5, 0], "this is fixed"[8, 5, 0], "this is float"[8, 5, 0], "end ing"[4, 0], "abc def"[4, 0], "begin start"[6, 0]
2323Test 5, LTR:
2424Move right by one word
2525"this is fixed"[0, 5, 8], "this is relative"[0, 5, 8], "this is absolute"[0, 5, 8, 16]
2626Move left by one word
27 "this is absolute"[16, 8, 5, 0], "this is relative"[8, 5, 0], "this is fixed"[8, 5], "this is float"[8, 5, 0], "end ing"[4, 0], "abc def"[4, 0], "begin start"[6, 0]
 27"this is absolute"[16, 8, 5, 0], "this is relative"[8, 5, 0], "this is fixed"[8, 5, 0], "this is float"[8, 5, 0], "end ing"[4, 0], "abc def"[4, 0], "begin start"[6, 0]
2828Test 6, LTR:
2929Move right by one word
3030"this is relative"[0, 5, 8], "this is absolute"[0, 5, 8, 16]
3131Move left by one word
32 "this is absolute"[16, 8, 5, 0], "this is relative"[8, 5, 0], "this is fixed"[8, 5], "this is float"[8, 5, 0], "end ing"[4, 0], "abc def"[4, 0], "begin start"[6, 0]
 32"this is absolute"[16, 8, 5, 0], "this is relative"[8, 5, 0], "this is fixed"[8, 5, 0], "this is float"[8, 5, 0], "end ing"[4, 0], "abc def"[4, 0], "begin start"[6, 0]
3333Test 7, LTR:
3434Move right by one word
3535"this is absolute"[0, 5, 8, 16]
3636Move left by one word
37 "this is absolute"[16, 8, 5, 0], "this is relative"[8, 5, 0], "this is fixed"[8, 5], "this is float"[8, 5, 0], "end ing"[4, 0], "abc def"[4, 0], "begin start"[6, 0]
 37"this is absolute"[16, 8, 5, 0], "this is relative"[8, 5, 0], "this is fixed"[8, 5, 0], "this is float"[8, 5, 0], "end ing"[4, 0], "abc def"[4, 0], "begin start"[6, 0]
3838
110473

LayoutTests/editing/selection/move-by-word-visually-inline-block-positioned-element.html

@@if (window.layoutTestController) {
1717
1818<div id="testMoveByWord" contenteditable style="width:2000px; height:2000px">
1919<div id="d_1" dir=ltr
20 title="[d_1, 0][d_1, 6][d_2, 0][d_2, 4][d_3, 0][d_3, 4][d_4, 0][d_4, 5][d_4, 8][d_5, 5][d_5, 8][d_6, 0][d_6, 5][d_6, 8][d_7, 0][d_7, 5][d_7, 8][d_7, 16]|
21 [d_7, 16][d_7, 8][d_7, 5][d_7, 0][d_6, 8][d_6, 5][d_6, 0][d_5, 8][d_5, 5][d_4, 8][d_4, 5][d_4, 0][d_3, 4][d_3, 0][d_2, 4][d_2, 0][d_1, 6][d_1, 0]"
 20title="[d_1, 0][d_1, 6][d_2, 0][d_2, 4][d_3, 0][d_3, 4][d_4, 0][d_4, 5][d_4, 8][d_5, 0][d_5, 5][d_5, 8][d_6, 0][d_6, 5][d_6, 8][d_7, 0][d_7, 5][d_7, 8][d_7, 16]|
 21[d_7, 16][d_7, 8][d_7, 5][d_7, 0][d_6, 8][d_6, 5][d_6, 0][d_5, 8][d_5, 5][d_5, 0][d_4, 8][d_4, 5][d_4, 0][d_3, 4][d_3, 0][d_2, 4][d_2, 0][d_1, 6][d_1, 0]"
2222class="test_move_by_word">begin start</div>
2323
2424<div id="d_2" dir=ltr
25 title="[d_2, 0][d_2, 4][d_3, 0][d_3, 4][d_4, 0][d_4, 5][d_4, 8][d_5, 5][d_5, 8][d_6, 0][d_6, 5][d_6, 8][d_7, 0][d_7, 5][d_7, 8][d_7, 16]|
26 [d_7, 16][d_7, 8][d_7, 5][d_7, 0][d_6, 8][d_6, 5][d_6, 0][d_5, 8][d_5, 5][d_4, 8][d_4, 5][d_4, 0][d_3, 4][d_3, 0][d_2, 4][d_2, 0][d_1, 6][d_1, 0]"
 25title="[d_2, 0][d_2, 4][d_3, 0][d_3, 4][d_4, 0][d_4, 5][d_4, 8][d_5, 0][d_5, 5][d_5, 8][d_6, 0][d_6, 5][d_6, 8][d_7, 0][d_7, 5][d_7, 8][d_7, 16]|
 26[d_7, 16][d_7, 8][d_7, 5][d_7, 0][d_6, 8][d_6, 5][d_6, 0][d_5, 8][d_5, 5][d_5, 0][d_4, 8][d_4, 5][d_4, 0][d_3, 4][d_3, 0][d_2, 4][d_2, 0][d_1, 6][d_1, 0]"
2727class="test_move_by_word" style="display:inline-block">abc def</div>
2828
2929<div id="d_3" dir=ltr
30 title="[d_3, 0][d_3, 4][d_4, 0][d_4, 5][d_4, 8][d_5, 5][d_5, 8][d_6, 0][d_6, 5][d_6, 8][d_7, 0][d_7, 5][d_7, 8][d_7, 16]|
31 [d_7, 16][d_7, 8][d_7, 5][d_7, 0][d_6, 8][d_6, 5][d_6, 0][d_5, 8][d_5, 5][d_4, 8][d_4, 5][d_4, 0][d_3, 4][d_3, 0][d_2, 4][d_2, 0][d_1, 6][d_1, 0]"
 30title="[d_3, 0][d_3, 4][d_4, 0][d_4, 5][d_4, 8][d_5, 0][d_5, 5][d_5, 8][d_6, 0][d_6, 5][d_6, 8][d_7, 0][d_7, 5][d_7, 8][d_7, 16]|
 31[d_7, 16][d_7, 8][d_7, 5][d_7, 0][d_6, 8][d_6, 5][d_6, 0][d_5, 8][d_5, 5][d_5, 0][d_4, 8][d_4, 5][d_4, 0][d_3, 4][d_3, 0][d_2, 4][d_2, 0][d_1, 6][d_1, 0]"
3232class="test_move_by_word">end ing</div>
3333
3434<div id="d_4" dir=ltr
35 title="[d_4, 0][d_4, 5][d_4, 8][d_5, 5][d_5, 8][d_6, 0][d_6, 5][d_6, 8][d_7, 0][d_7, 5][d_7, 8][d_7, 16]|
36 [d_7, 16][d_7, 8][d_7, 5][d_7, 0][d_6, 8][d_6, 5][d_6, 0][d_5, 8][d_5, 5][d_4, 8][d_4, 5][d_4, 0][d_3, 4][d_3, 0][d_2, 4][d_2, 0][d_1, 6][d_1, 0]"
 35title="[d_4, 0][d_4, 5][d_4, 8][d_5, 0][d_5, 5][d_5, 8][d_6, 0][d_6, 5][d_6, 8][d_7, 0][d_7, 5][d_7, 8][d_7, 16]|
 36[d_7, 16][d_7, 8][d_7, 5][d_7, 0][d_6, 8][d_6, 5][d_6, 0][d_5, 8][d_5, 5][d_5, 0][d_4, 8][d_4, 5][d_4, 0][d_3, 4][d_3, 0][d_2, 4][d_2, 0][d_1, 6][d_1, 0]"
3737class="test_move_by_word" style="float:left">this is float</div>
3838
3939<div id="d_5" dir=ltr
4040title="[d_5, 0][d_5, 5][d_5, 8][d_6, 0][d_6, 5][d_6, 8][d_7, 0][d_7, 5][d_7, 8][d_7, 16]|
41 [d_7, 16][d_7, 8][d_7, 5][d_7, 0][d_6, 8][d_6, 5][d_6, 0][d_5, 8][d_5, 5][d_4, 8][d_4, 5][d_4, 0][d_3, 4][d_3, 0][d_2, 4][d_2, 0][d_1, 6][d_1, 0]"
 41[d_7, 16][d_7, 8][d_7, 5][d_7, 0][d_6, 8][d_6, 5][d_6, 0][d_5, 8][d_5, 5][d_5, 0][d_4, 8][d_4, 5][d_4, 0][d_3, 4][d_3, 0][d_2, 4][d_2, 0][d_1, 6][d_1, 0]"
4242class="test_move_by_word" style="position:fixed; top:30px; right:5px">this is fixed</div>
4343
4444<div id="d_6" dir=ltr
4545title="[d_6, 0][d_6, 5][d_6, 8][d_7, 0][d_7, 5][d_7, 8][d_7, 16]|
46 [d_7, 16][d_7, 8][d_7, 5][d_7, 0][d_6, 8][d_6, 5][d_6, 0][d_5, 8][d_5, 5][d_4, 8][d_4, 5][d_4, 0][d_3, 4][d_3, 0][d_2, 4][d_2, 0][d_1, 6][d_1, 0]"
 46[d_7, 16][d_7, 8][d_7, 5][d_7, 0][d_6, 8][d_6, 5][d_6, 0][d_5, 8][d_5, 5][d_5, 0][d_4, 8][d_4, 5][d_4, 0][d_3, 4][d_3, 0][d_2, 4][d_2, 0][d_1, 6][d_1, 0]"
4747class="test_move_by_word" style="position:relative; left:20px">this is relative</div>
4848
4949<div id="d_7" dir=ltr
5050title="[d_7, 0][d_7, 5][d_7, 8][d_7, 16]|
51 [d_7, 16][d_7, 8][d_7, 5][d_7, 0][d_6, 8][d_6, 5][d_6, 0][d_5, 8][d_5, 5][d_4, 8][d_4, 5][d_4, 0][d_3, 4][d_3, 0][d_2, 4][d_2, 0][d_1, 6][d_1, 0]"
 51[d_7, 16][d_7, 8][d_7, 5][d_7, 0][d_6, 8][d_6, 5][d_6, 0][d_5, 8][d_5, 5][d_5, 0][d_4, 8][d_4, 5][d_4, 0][d_3, 4][d_3, 0][d_2, 4][d_2, 0][d_1, 6][d_1, 0]"
5252class="test_move_by_word" style="position:absolute; left:100px; top:150px">this is absolute</div>
5353
5454</div>
110473

LayoutTests/editing/selection/move-by-word-visually-multi-line-expected.txt

@@Move right by one word
3737"opq rst uvw xyz"[15, 11, 8, 3, 0], "abc def ghi jkl mn "[16, 11, 8, 3, 0]
3838Test 8, RTL:
3939Move left by one word
40 "abc def ghi jkl mn "[0, 3, 8, 11, 16, 18], "opq rst uvw xyz"[0, 3, 8, 11, 15] FAIL expected: ["abc def ghi jkl mn "[ 0, 3, 8, 11, 16, ]"opq rst uvw xyz"[ 0, 3, 8, 11, 15]
41 "abc def ghi jkl mn "[16, 18] FAIL expected "opq rst uvw xyz"[ 0]
42 "abc def ghi jkl mn "[17, 18] FAIL expected "opq rst uvw xyz"[ 0]
 40"abc def ghi jkl mn "[0, 3, 8, 11, 16], "opq rst uvw xyz"[0, 3, 8, 11, 15]
4341Move right by one word
44 "opq rst uvw xyz"[15, 11, 8, 3, 0], "abc def ghi jkl mn "[18, 16, 11, 8, 3, 0]
 42"opq rst uvw xyz"[15, 11, 8, 3, 0], "abc def ghi jkl mn "[16, 11, 8, 3, 0]
4543Test 9, RTL:
4644Move left by one word
4745"AAA AAA AAA AAA "[0, 4, 8, 12], "AAA AAA AAA AAA"[0, 4, 8, 12, 15]

@@Move right by one word
5957"AAA kj AAA mn opq AAA AAA"[25, 22, 18, 14, 11, 7, 4, 0], " abc def AAA AAA hij AAA AAA uvw xyz "[33, 29, 25, 21, 17, 13, 9, 4, 1]
6058Test 12, RTL:
6159Move left by one word
62 " abc def AAA AAA hij AAA AAA uvw xyz "[1, 4, 9, 13, 17, 21, 25, 29, 33, 36], "AAA kj AAA mn opq AAA AAA"[0, 4, 7, 11, 14, 18, 22, 25] FAIL expected: [" abc def AAA AAA hij AAA AAA uvw xyz "[ 1, 4, 9, 13, 17, 21, 25, 29, 33, ]"AAA kj AAA mn opq AAA AAA"[ 0, 4, 7, 11, 14, 18, 22, 25]
63 " abc def AAA AAA hij AAA AAA uvw xyz "[33, 36] FAIL expected "AAA kj AAA mn opq AAA AAA"[ 0]
64 " abc def AAA AAA hij AAA AAA uvw xyz "[35, 36] FAIL expected "AAA kj AAA mn opq AAA AAA"[ 0]
65 " abc def AAA AAA hij AAA AAA uvw xyz "[34, 36] FAIL expected "AAA kj AAA mn opq AAA AAA"[ 0]
 60" abc def AAA AAA hij AAA AAA uvw xyz "[1, 4, 9, 13, 17, 21, 25, 29, 33], "AAA kj AAA mn opq AAA AAA"[0, 4, 7, 11, 14, 18, 22, 25]
6661Move right by one word
67 "AAA kj AAA mn opq AAA AAA"[25, 22, 18, 14, 11, 7, 4, 0], " abc def AAA AAA hij AAA AAA uvw xyz "[36, 33, 29, 25, 21, 17, 13, 9, 4, 1]
 62"AAA kj AAA mn opq AAA AAA"[25, 22, 18, 14, 11, 7, 4, 0], " abc def AAA AAA hij AAA AAA uvw xyz "[33, 29, 25, 21, 17, 13, 9, 4, 1]
6863Test 13, LTR:
6964Move right by one word
70 "abc def "[0, 4]
 65"abc def ghi jkl mn "[0, 4, 8, 12, 16], "opq rst uvw xyz"[0, 4, 8, 12, 15]
7166Move left by one word
72 " hij opq"[8, 5, 1]
 67"opq rst uvw xyz"[15, 12, 8, 4, 0], "abc def ghi jkl mn "[16, 12, 8, 4, 0]
7368Test 14, LTR:
7469Move right by one word
 70"abc def ghi jkl mn "[0, 4, 8, 12, 16, 18]
 71Move left by one word
 72"abc def ghi jkl mn "[18, 16, 12, 8, 4, 0]
 73Test 15, LTR:
 74Move right by one word
 75"abc def ghi jkl mn "[0, 4, 8, 12, 16], "opq rst uvw xyz"[0, 4, 8, 12, 15]
 76Move left by one word
 77"opq rst uvw xyz"[15, 12, 8, 4, 0], "abc def ghi jkl mn "[16, 12, 8, 4, 0]
 78Test 16, LTR:
 79Move right by one word
 80"abc def "[0, 4, 8]
 81Move left by one word
 82" hij opq"[8, 5, 1]
 83Test 17, LTR:
 84Move right by one word
7585<DIV>[0]
7686Move left by one word
7787<DIV>[0]
78 Test 15, LTR:
 88Test 18, LTR:
7989Move right by one word
8090"\n00"[3]
8191Move left by one word
110473

LayoutTests/editing/selection/move-by-word-visually-multi-line.html

@@where child_node_index is optional, defa
5454
5555<div contenteditable dir=rtl id="ml_7" class="test_move_by_word fix_width" title="[ml_7, 15, 5][ml_7, 11, 5][ml_7, 8, 5][ml_7, 3, 5][ml_7, 0, 5][ml_7, 16][ml_7, 11][ml_7, 8][ml_7, 3][ml_7, 0]|[ml_7, 0][ml_7, 3][ml_7, 8][ml_7, 11][ml_7, 16][ml_7, 0, 5][ml_7, 3, 5][ml_7, 8, 5][ml_7, 11, 5][ml_7, 15, 5]">abc def ghi jkl mn <br/><br/><br/>opq rst uvw xyz</div>
5656
57 <div contenteditable dir=rtl id="ml_8" class="test_move_by_word fix_width" title="[ml_8, 15, 5][ml_8, 11, 5][ml_8, 8, 5][ml_8, 3, 5][ml_8, 0, 5][ml_8, 18][ml_8, 16][ml_8, 11][ml_8, 8][ml_8, 3][ml_8, 0]|[ml_8, 0][ml_8, 3][ml_8, 8][ml_8, 11][ml_8, 16][ml_8, 0, 5][ml_8, 3, 5][ml_8, 8, 5][ml_8, 11, 5][ml_8, 15, 5]">abc def ghi jkl mn <div><br/></div><div><br/></div><div><br/></div>opq rst uvw xyz</div>
 57<div contenteditable dir=rtl id="ml_8" class="test_move_by_word fix_width" title="[ml_8, 15, 5][ml_8, 11, 5][ml_8, 8, 5][ml_8, 3, 5][ml_8, 0, 5][ml_8, 16][ml_8, 11][ml_8, 8][ml_8, 3][ml_8, 0]|[ml_8, 0][ml_8, 3][ml_8, 8][ml_8, 11][ml_8, 16][ml_8, 0, 5][ml_8, 3, 5][ml_8, 8, 5][ml_8, 11, 5][ml_8, 15, 5]">abc def ghi jkl mn <div><br/></div><div><br/></div><div><br/></div>opq rst uvw xyz</div>
5858
5959<div contenteditable dir=rtl id="ml_9" class="test_move_by_word fix_width" title="[ml_9, 15, 5][ml_9, 12, 5][ml_9, 8, 5][ml_9, 4, 5][ml_9, 0, 5][ml_9, 12][ml_9, 8][ml_9, 4][ml_9, 0]|[ml_9, 0][ml_9, 4][ml_9, 8][ml_9, 12][ml_9, 0, 5][ml_9, 4, 5][ml_9, 8, 5][ml_9, 12, 5][ml_9, 15, 5]">אאא אאא אאא אאא <br/><br/><br/>אאא אאא אאא אאא</div>
6060

@@where child_node_index is optional, defa
6565"> abc def אאא אאא hij אאא אאא uvw xyz <br/><br/><br/>אאא kj אאא mn opq אאא אאא</div>
6666
6767<div contenteditable dir=rtl id="ml_12" class="test_move_by_word fix_width" title="
68 [ml_12, 25, 5][ml_12, 22, 5][ml_12, 18, 5][ml_12, 14, 5][ml_12, 11, 5][ml_12, 7, 5][ml_12, 4, 5][ml_12, 0, 5][ml_12, 36][ml_12, 33][ml_12, 29][ml_12, 25][ml_12, 21][ml_12, 17][ml_12, 13][ml_12, 9][ml_12, 4][ml_12, 1]|[ml_12, 1][ml_12, 4][ml_12, 9][ml_12, 13][ml_12, 17][ml_12, 21][ml_12, 25][ml_12, 29][ml_12, 33][ml_12, 0, 5][ml_12, 4, 5][ml_12, 7, 5][ml_12, 11, 5][ml_12, 14, 5][ml_12, 18, 5][ml_12, 22, 5][ml_12, 25, 5]
 68[ml_12, 25, 5][ml_12, 22, 5][ml_12, 18, 5][ml_12, 14, 5][ml_12, 11, 5][ml_12, 7, 5][ml_12, 4, 5][ml_12, 0, 5][ml_12, 33][ml_12, 29][ml_12, 25][ml_12, 21][ml_12, 17][ml_12, 13][ml_12, 9][ml_12, 4][ml_12, 1]|[ml_12, 1][ml_12, 4][ml_12, 9][ml_12, 13][ml_12, 17][ml_12, 21][ml_12, 25][ml_12, 29][ml_12, 33][ml_12, 0, 5][ml_12, 4, 5][ml_12, 7, 5][ml_12, 11, 5][ml_12, 14, 5][ml_12, 18, 5][ml_12, 22, 5][ml_12, 25, 5]
6969"> abc def אאא אאא hij אאא אאא uvw xyz <div><br/></div><div><br/></div><div><br/></div>אאא kj אאא mn opq אאא אאא</div>
7070
 71<div contenteditable dir=ltr id="ml_13" class="test_move_by_word fix_width" title="[ml_13, 0][ml_13, 4][ml_13, 8][ml_13, 12][ml_13, 16][ml_13, 0, 5][ml_13, 4, 5][ml_13, 8, 5][ml_13, 12, 5][ml_13, 15, 5]|[ml_13, 15, 5][ml_13, 12, 5][ml_13, 8, 5][ml_13, 4, 5][ml_13, 0, 5][ml_13, 16][ml_13, 12][ml_13, 8][ml_13, 4][ml_13, 0]">abc def ghi jkl mn <div></div><div></div><div></div>opq rst uvw xyz</div>
 72
 73<div contenteditable dir=ltr id="ml_14" class="test_move_by_word fix_width" title="[ml_14, 0][ml_14, 4][ml_14, 8][ml_14, 12][ml_14, 16][ml_14, 18]|[ml_14, 18][ml_14, 16][ml_14, 12][ml_14, 8][ml_14, 4][ml_14, 0]">abc def ghi jkl mn <div></div><div></div><div></div></div>
 74
 75<div contenteditable dir=ltr id="ml_15" class="test_move_by_word fix_width" title="[ml_15, 0][ml_15, 4][ml_15, 8][ml_15, 12][ml_15, 16][ml_15, 0, 5][ml_15, 4, 5][ml_15, 8, 5][ml_15, 12, 5][ml_15, 15, 5]|[ml_15, 15, 5][ml_15, 12, 5][ml_15, 8, 5][ml_15, 4, 5][ml_15, 0, 5][ml_15, 16][ml_15, 12][ml_15, 8][ml_15, 4][ml_15, 0]">abc def ghi jkl mn <div><img src=../../accessibility/resources/cake.png></div><div></div><div></div>opq rst uvw xyz</div>
 76
7177<!-- mixed editability -->
72 <div dir=ltr class="test_move_by_word" title="0 4|8 5 1">abc def <span contenteditable> inside span </span> hij opq</div>
 78<div dir=ltr class="test_move_by_word" title="0 4 8|8 5 1">abc def <span contenteditable> inside span </span> hij opq</div>
7379
7480<div class="test_move_by_word" contenteditable dir=ltr title="0|0"></div>
7581
110473

LayoutTests/editing/selection/move-by-word-visually-single-space-inline-element-expected.txt

@@Move left by one word
1717" rst uvw"[8, 5, 1], "hij opq"[4], "abc def "[8, 4, 0]
1818Test 4, RTL:
1919Move left by one word
20 "abc def "[0], " rst uvw"[4], "hij opq"[3], "abc def "[7, 3], " rst uvw"[8] FAIL expected: ["abc def "[ 0, ]" rst uvw"[ 4, ]"hij opq"[ 7, 3, ]"abc def "[ 7, 3, ]" rst uvw"[ 8]
21 " rst uvw"[4], "hij opq"[3] FAIL expected "hij opq"[ 7]
22 " rst uvw"[3], "hij opq"[3] FAIL expected "hij opq"[ 7]
23 " rst uvw"[2], "hij opq"[3] FAIL expected "hij opq"[ 7]
24 " rst uvw"[1], "hij opq"[3] FAIL expected "hij opq"[ 7]
25 Move right by one word
26 " 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, 7, ]" rst uvw"[ 4, ]"abc def "[ 0]
27 "hij opq"[3], " rst uvw"[4] FAIL expected "hij opq"[ 7]
28 "hij opq"[4], " rst uvw"[4] FAIL expected "hij opq"[ 7]
29 "hij opq"[5], " rst uvw"[4] FAIL expected "hij opq"[ 7]
30 "hij opq"[6], " rst uvw"[4] FAIL expected "hij opq"[ 7]
 20"abc def "[0], " rst uvw"[4], "hij opq"[7, 3], "abc def "[7, 3], " rst uvw"[8]
 21Move right by one word
 22" rst uvw"[8], "abc def "[3, 7], "hij opq"[3, 7], " rst uvw"[4], "abc def "[0]
3123Test 5, RTL:
3224Move left by one word
33 "abc def "[0], " rst uvw"[4], "hij opq"[3], "abc def "[7, 3], " rst uvw"[8] FAIL expected: ["abc def "[ 0, ]" rst uvw"[ 4, ]"hij opq"[ 7, 3, ]"abc def "[ 7, 3, ]" rst uvw"[ 8]
34 " rst uvw"[4], "hij opq"[3] FAIL expected "hij opq"[ 7]
35 " rst uvw"[3], "hij opq"[3] FAIL expected "hij opq"[ 7]
36 " rst uvw"[2], "hij opq"[3] FAIL expected "hij opq"[ 7]
37 " rst uvw"[1], "hij opq"[3] FAIL expected "hij opq"[ 7]
38 Move right by one word
39 " 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, 7, ]" rst uvw"[ 4, ]"abc def "[ 0]
40 "hij opq"[3], " rst uvw"[4] FAIL expected "hij opq"[ 7]
41 "hij opq"[4], " rst uvw"[4] FAIL expected "hij opq"[ 7]
42 "hij opq"[5], " rst uvw"[4] FAIL expected "hij opq"[ 7]
43 "hij opq"[6], " rst uvw"[4] FAIL expected "hij opq"[ 7]
 25"abc def "[0], " rst uvw"[4], "hij opq"[7, 3], "abc def "[7, 3], " rst uvw"[8]
 26Move right by one word
 27" rst uvw"[8], "abc def "[3, 7], "hij opq"[3, 7], " rst uvw"[4], "abc def "[0]
4428Test 6, LTR:
4529Move right by one word
4630"abc def "[0, 4, 8], "hij opq"[4], " rst uvw"[1, 5, 8]

@@Move left by one word
8872"FFZ LIG"[7, 3], "abc def"[4], "ABD DSU "[8, 3, 0]
8973Test 15, RTL:
9074Move left by one word
91 "ABD opq DSU "[0, 4, 8, 12], "abc AAA def"[8, 4, 3], "FFZ rst LIG"[4, 8, 11] FAIL expected: ["ABD opq DSU "[ 0, 4, 8, 12, ]"abc AAA def"[ 4, 3, ]"FFZ rst LIG"[ 4, 8, 11]
92 "ABD opq DSU "[12], "abc AAA def"[8] FAIL expected "abc AAA def"[ 4]
93 "abc AAA def"[10, 8] FAIL expected "abc AAA def"[ 4]
94 "abc AAA def"[9, 8] FAIL expected "abc AAA def"[ 4]
 75"ABD opq DSU "[0, 4, 8, 12], "abc AAA def"[4, 3], "FFZ rst LIG"[4, 8, 11]
9576Move right by one word
96 "FFZ rst LIG"[11, 8, 4], "abc AAA def"[3, 4, 8], "ABD opq DSU "[12, 8, 4, 0] FAIL expected: ["FFZ rst LIG"[ 11, 8, 4, ]"abc AAA def"[ 3, 4, ]"ABD opq DSU "[ 12, 8, 4, 0]
97 "abc AAA def"[4, 8] FAIL expected "ABD opq DSU "[ 12]
 77"FFZ rst LIG"[11, 8, 4], "abc AAA def"[3, 4], "ABD opq DSU "[12, 8, 4, 0]
9878Test 16, LTR:
9979Move right by one word
100 "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]
101 "ABD opq DSU "[4], "abc AAA def"[8] FAIL expected "ABD opq DSU "[ 8]
102 "ABD opq DSU "[5], "abc AAA def"[8] FAIL expected "ABD opq DSU "[ 8]
103 "ABD opq DSU "[6], "abc AAA def"[8] FAIL expected "ABD opq DSU "[ 8]
104 "ABD opq DSU "[7], "abc AAA def"[8] FAIL expected "ABD opq DSU "[ 8]
105 "abc AAA def"[8, 4] FAIL expected "abc AAA def"[ 7]
106 "abc AAA def"[9, 4] FAIL expected "abc AAA def"[ 7]
107 "abc AAA def"[10, 4] FAIL expected "abc AAA def"[ 7]
108 "abc AAA def"[11, 4] FAIL expected "abc AAA def"[ 7]
109 "abc AAA def"[7, 4] FAIL expected "ABD opq DSU "[ 12]
110 "abc AAA def"[6, 4] FAIL expected "ABD opq DSU "[ 12]
111 "abc AAA def"[5, 4] FAIL expected "ABD opq DSU "[ 12]
112 Move left by one word
113 "FFZ rst LIG"[11, 8, 4], "ABD opq DSU "[11, 12], "abc AAA def"[7, 8], "ABD opq DSU "[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]
114 "abc AAA def"[8], "ABD opq DSU "[4] FAIL expected "ABD opq DSU "[ 8]
115 "FFZ rst LIG"[1], "ABD opq DSU "[4] FAIL expected "ABD opq DSU "[ 8]
116 "FFZ rst LIG"[2], "ABD opq DSU "[4] FAIL expected "ABD opq DSU "[ 8]
 80"ABD opq DSU "[0, 4, 8], "abc AAA def"[8, 7], "ABD opq DSU "[12, 11], "FFZ rst LIG"[4, 8, 11]
 81Move left by one word
 82"FFZ rst LIG"[11, 8, 4], "ABD opq DSU "[11, 12], "abc AAA def"[7, 8], "ABD opq DSU "[8, 4, 0]
11783Test 17, RTL:
11884Move left by one word
11985"ABD opq DSU "[0, 4, 8, 12], "abc AAA def"[4, 8], "FFZ rst LIG"[4, 8, 11]

@@Move left by one word
13197"FFZ"[3], "bbb AAA "[7, 4], "aaa "[4, 0]
13298Test 20, RTL:
13399Move left by one word
134 "ABD opq rst DSU "[0, 4, 7, 12, 16], "abc uvw AAA def lmn"[12, 8, 7, 3], "ABW hij xyz FXX"[4, 7, 12, 15] FAIL expected: ["ABD opq rst DSU "[ 0, 4, 7, 12, 16, ]"abc uvw AAA def lmn"[ 15, 8, 7, 3, ]"ABW hij xyz FXX"[ 4, 7, 12, 15]
135 "ABD opq rst DSU "[16], "abc uvw AAA def lmn"[12] FAIL expected "abc uvw AAA def lmn"[ 15]
136 "abc uvw AAA def lmn"[18, 12] FAIL expected "abc uvw AAA def lmn"[ 15]
137 "abc uvw AAA def lmn"[17, 12] FAIL expected "abc uvw AAA def lmn"[ 15]
138 "abc uvw AAA def lmn"[16, 12] FAIL expected "abc uvw AAA def lmn"[ 15]
139 "abc uvw AAA def lmn"[15, 12] FAIL expected "abc uvw AAA def lmn"[ 8]
140 "abc uvw AAA def lmn"[14, 12] FAIL expected "abc uvw AAA def lmn"[ 8]
141 "abc uvw AAA def lmn"[13, 12] FAIL expected "abc uvw AAA def lmn"[ 8]
 100"ABD opq rst DSU "[0, 4, 7, 12, 16], "abc uvw AAA def lmn"[15, 8, 7, 3], "ABW hij xyz FXX"[4, 7, 12, 15]
142101Move right by one word
143102"ABW hij xyz FXX"[15, 12, 7, 4], "abc uvw AAA def lmn"[3, 7, 8, 15], "ABD opq rst DSU "[16, 12, 7, 4, 0]
144103
110473

LayoutTests/editing/selection/move-by-word-visually-single-space-one-element-expected.txt

@@Move right by one word
8282"ZQB abc RIG"[11, 8, 4, 0]
8383Test 17, LTR:
8484Move right by one word
 85"abc ᪜BAD def᪝ xyz"[0, 8, 14, 17] FAIL expected: [0, 9, 8, 14, 17]
 86"abc ᪜BAD def᪝ xyz"[0, 8] FAIL expected 9
 87"abc ᪜BAD def᪝ xyz"[1, 8] FAIL expected 9
 88"abc ᪜BAD def᪝ xyz"[2, 8] FAIL expected 9
 89"abc ᪜BAD def᪝ xyz"[3, 8] FAIL expected 9
 90"abc ᪜BAD def᪝ xyz"[4, 8] FAIL expected 9
 91Move left by one word
 92"abc ᪜BAD def᪝ xyz"[17, 14, 8, 9, 0]
 93Test 18, LTR:
 94Move right by one word
 95"abc def hij "[0, 4, 8], " opq rst "[1, 5, 8]
 96Move left by one word
 97" opq rst "[8, 5, 1], "abc def hij "[8, 4, 0]
 98Test 19, LTR:
 99Move right by one word
85100<DIV>[0]
86101Move left by one word
87102<DIV>[0]
110473

LayoutTests/editing/selection/move-by-word-visually-single-space-one-element.html

@@where child_node_index is optional, defa
6565<div dir=ltr class="test_move_by_word" title="0 4 8 11|11 8 4 0" contenteditable>שנב abc סטז</div>
6666<div dir=rtl class="test_move_by_word" title="11 8 4 0|0 4 8 11" contenteditable>שנב abc סטז</div>
6767
 68<!-- Test with Bidi control characters -->
 69<div dir=ltr class="test_move_by_word" id="notReachablePosition" title="0 9 8 14 17|17 14 8 9 0" contenteditable>abc &#x202b;באד def&#x202c; xyz</div>
 70
 71<!-- Test with image -- non-inline-text-box -->
 72<div id="d_1" dir=ltr class="test_move_by_word" contenteditable title="[d_1, 0, 1][d_1, 4, 1][d_1, 8, 1][d_1, 1, 3][d_1, 5, 3][d_1, 8, 3]|[d_1, 8, 3][d_1, 5, 3][d_1, 1, 3][d_1, 8, 1][d_1, 4, 1][d_1, 0, 1]">abc def hij <img src=../../accessibility/resources/cake.png> opq rst </div>
 73
6874<!-- empty div -->
6975<div dir=ltr class="test_move_by_word" title="0|0" contenteditable></div>
7076
110473

LayoutTests/editing/selection/move-by-word-visually-wrong-left-right-expected.txt

 1abc ששש def שנב opq סטז uvw ששש xyz
 2ctrl/alt+right from left of סטז should move cursor to left of opq. But it actually moved to position 4
0

LayoutTests/editing/selection/move-by-word-visually-wrong-left-right.html

 1<!DOCTYPE HTML>
 2<html>
 3<head>
 4<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
 5<link rel="stylesheet" type="text/css" href="resources/extend-selection.css" />
 6<script src="resources/move-by-word-visually.js"></script>
 7<script>
 8onload = function() {
 9 try {
 10 var sel = window.getSelection();
 11 sel.setPosition(document.getElementById('d_1'), 0);
 12 sel.modify("move", "right", "word");
 13 if (sel.anchorOffset == 5)
 14 log("PASS");
 15 else
 16 log("ctrl/alt+right from left of סטז should move cursor to left of opq. " +
 17 "But it actually moved to position " + sel.anchorOffset);
 18 } finally {
 19 flushLog();
 20 }
 21};
 22
 23if (window.layoutTestController) {
 24 layoutTestController.dumpAsText();
 25 layoutTestController.setEditingBehavior('win');
 26}
 27</script>
 28<title>wrong result because VisiblePosition.left()/right() returns wrong result</title>
 29</head>
 30<body>
 31<div id="testMoveByWord">
 32<div dir=ltr contenteditable>abc ששש def <span id="d_1" dir=rtl>שנב opq סטז</span> uvw ששש xyz</div>
 33</div>
 34<ul id="console"></ul>
 35</body>
 36</html>
0

LayoutTests/editing/selection/resources/move-by-word-visually.js

@@function moveByWordOnEveryChar(sel, test
175175 var prevOffset = sel.anchorOffset;
176176 var prevNode = sel.anchorNode;
177177
 178 // advance is used to special handling the case that arrow key is not able to reach certain position.
 179 // In which case, we will need to manually skip this word break position in order to report correct log.
 180 var advance = true;
 181
178182 while (1) {
179183 var positions = [];
180184 positions.push({ node: sel.anchorNode, offset: sel.anchorOffset });

@@function moveByWordOnEveryChar(sel, test
205209 break;
206210
207211 position = { node: sel.anchorNode, offset: sel.anchorOffset };
208  if (wordBreakIndex < wordBreaks.length
 212 if ((wordBreakIndex < wordBreaks.length
209213 && positionEqualToWordBreak(position, wordBreaks[wordBreakIndex]))
 214 || (test == document.getElementById("notReachablePosition")
 215 && sel.anchorOffset > wordBreaks[wordBreakIndex]
 216 && advance
 217 && searchDirection == "right")) {
210218 ++wordBreakIndex;
 219 advance = false;
 220 }
211221
212222 prevNode = sel.anchorNode;
213223 prevOffset = sel.anchorOffset;
110473