| OLD | NEW |
| (Empty) | |
| 1 // Copyright (c) 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "core/editing/commands/InsertIncrementalTextCommand.h" |
| 6 |
| 7 #include "core/dom/Document.h" |
| 8 #include "core/dom/Element.h" |
| 9 #include "core/dom/Text.h" |
| 10 #include "core/editing/EditingUtilities.h" |
| 11 #include "core/editing/Editor.h" |
| 12 #include "core/editing/PlainTextRange.h" |
| 13 #include "core/editing/VisibleUnits.h" |
| 14 #include "core/frame/LocalFrame.h" |
| 15 #include "core/html/HTMLSpanElement.h" |
| 16 |
| 17 namespace blink { |
| 18 |
| 19 InsertIncrementalTextCommand::InsertIncrementalTextCommand( |
| 20 Document& document, |
| 21 const String& text, |
| 22 bool selectInsertedText, |
| 23 RebalanceType rebalanceType) |
| 24 : CompositeEditCommand(document), |
| 25 m_text(text), |
| 26 m_selectInsertedText(selectInsertedText), |
| 27 m_rebalanceType(rebalanceType) {} |
| 28 |
| 29 String InsertIncrementalTextCommand::textDataForInputEvent() const { |
| 30 return m_text; |
| 31 } |
| 32 |
| 33 Position InsertIncrementalTextCommand::positionInsideTextNode( |
| 34 const Position& p, |
| 35 EditingState* editingState) { |
| 36 Position pos = p; |
| 37 if (isTabHTMLSpanElementTextNode(pos.anchorNode())) { |
| 38 Text* textNode = document().createEditingTextNode(""); |
| 39 insertNodeAtTabSpanPosition(textNode, pos, editingState); |
| 40 if (editingState->isAborted()) |
| 41 return Position(); |
| 42 return Position::firstPositionInNode(textNode); |
| 43 } |
| 44 |
| 45 // Prepare for text input by looking at the specified position. |
| 46 // It may be necessary to insert a text node to receive characters. |
| 47 if (!pos.computeContainerNode()->isTextNode()) { |
| 48 Text* textNode = document().createEditingTextNode(""); |
| 49 insertNodeAt(textNode, pos, editingState); |
| 50 if (editingState->isAborted()) |
| 51 return Position(); |
| 52 return Position::firstPositionInNode(textNode); |
| 53 } |
| 54 |
| 55 return pos; |
| 56 } |
| 57 |
| 58 void InsertIncrementalTextCommand::setEndingSelectionWithoutValidation( |
| 59 const Position& startPosition, |
| 60 const Position& endPosition) { |
| 61 // We could have inserted a part of composed character sequence, |
| 62 // so we are basically treating ending selection as a range to avoid |
| 63 // validation. <http://bugs.webkit.org/show_bug.cgi?id=15781> |
| 64 setEndingSelection(SelectionInDOMTree::Builder() |
| 65 .collapse(startPosition) |
| 66 .extend(endPosition) |
| 67 .setIsDirectional(endingSelection().isDirectional()) |
| 68 .build()); |
| 69 } |
| 70 |
| 71 // This avoids the expense of a full fledged delete operation, and avoids a |
| 72 // layout that typically results from text removal. |
| 73 bool InsertIncrementalTextCommand::performTrivialReplace( |
| 74 const String& text, |
| 75 bool selectInsertedText) { |
| 76 if (!endingSelection().isRange()) |
| 77 return false; |
| 78 |
| 79 if (text.contains('\t') || text.contains(' ') || text.contains('\n')) |
| 80 return false; |
| 81 |
| 82 Position start = endingSelection().start(); |
| 83 Position endPosition = replaceSelectedTextInNode(text); |
| 84 if (endPosition.isNull()) |
| 85 return false; |
| 86 |
| 87 setEndingSelectionWithoutValidation(start, endPosition); |
| 88 if (selectInsertedText) |
| 89 return true; |
| 90 setEndingSelection(SelectionInDOMTree::Builder() |
| 91 .collapse(endingSelection().end()) |
| 92 .setIsDirectional(endingSelection().isDirectional()) |
| 93 .build()); |
| 94 return true; |
| 95 } |
| 96 |
| 97 bool InsertIncrementalTextCommand::performOverwrite(const String& text, |
| 98 bool selectInsertedText) { |
| 99 Position start = endingSelection().start(); |
| 100 if (start.isNull() || !start.isOffsetInAnchor() || |
| 101 !start.computeContainerNode()->isTextNode()) |
| 102 return false; |
| 103 Text* textNode = toText(start.computeContainerNode()); |
| 104 if (!textNode) |
| 105 return false; |
| 106 |
| 107 unsigned count = std::min(text.length(), |
| 108 textNode->length() - start.offsetInContainerNode()); |
| 109 if (!count) |
| 110 return false; |
| 111 |
| 112 replaceTextInNode(textNode, start.offsetInContainerNode(), count, text); |
| 113 |
| 114 Position endPosition = |
| 115 Position(textNode, start.offsetInContainerNode() + text.length()); |
| 116 setEndingSelectionWithoutValidation(start, endPosition); |
| 117 if (selectInsertedText || endingSelection().isNone()) |
| 118 return true; |
| 119 setEndingSelection(SelectionInDOMTree::Builder() |
| 120 .collapse(endingSelection().end()) |
| 121 .setIsDirectional(endingSelection().isDirectional()) |
| 122 .build()); |
| 123 return true; |
| 124 } |
| 125 |
| 126 static size_t computeCommonPrefixLength(const String& str1, |
| 127 const String& str2) { |
| 128 const size_t maxCommonPrefixLength = std::min(str1.length(), str2.length()); |
| 129 for (size_t index = 0; index < maxCommonPrefixLength; ++index) { |
| 130 if (str1[index] != str2[index]) |
| 131 return index; |
| 132 } |
| 133 return maxCommonPrefixLength; |
| 134 } |
| 135 |
| 136 static size_t computeCommonSuffixLength(const String& str1, |
| 137 const String& str2) { |
| 138 const size_t length1 = str1.length(); |
| 139 const size_t length2 = str2.length(); |
| 140 const size_t maxCommonSuffixLength = std::min(length1, length2); |
| 141 for (size_t index = 0; index < maxCommonSuffixLength; ++index) { |
| 142 if (str1[length1 - index - 1] != str2[length2 - index - 1]) |
| 143 return index; |
| 144 } |
| 145 return maxCommonSuffixLength; |
| 146 } |
| 147 |
| 148 // If current position is at grapheme boundary, return 0; otherwise, return the |
| 149 // distance to its nearest left grapheme boundary. |
| 150 static size_t computeDistanceToLeftGraphemeBoundary(const Position& position) { |
| 151 const Position& adjustedPosition = previousPositionOf( |
| 152 nextPositionOf(position, PositionMoveType::GraphemeCluster), |
| 153 PositionMoveType::GraphemeCluster); |
| 154 DCHECK_EQ(position.anchorNode(), adjustedPosition.anchorNode()); |
| 155 DCHECK_GE(position.computeOffsetInContainerNode(), |
| 156 adjustedPosition.computeOffsetInContainerNode()); |
| 157 return static_cast<size_t>(position.computeOffsetInContainerNode() - |
| 158 adjustedPosition.computeOffsetInContainerNode()); |
| 159 } |
| 160 |
| 161 static size_t computeCommonGraphemeClusterPrefixLength( |
| 162 const String& oldText, |
| 163 const String& newText, |
| 164 const Element* rootEditableElement) { |
| 165 const size_t commonPrefixLength = computeCommonPrefixLength(oldText, newText); |
| 166 |
| 167 // For grapheme cluster, we should adjust it for grapheme boundary. |
| 168 const EphemeralRange& range = |
| 169 PlainTextRange(0, commonPrefixLength).createRange(*rootEditableElement); |
| 170 if (range.isNull()) |
| 171 return 0; |
| 172 const Position& position = range.endPosition(); |
| 173 const size_t diff = computeDistanceToLeftGraphemeBoundary(position); |
| 174 DCHECK_GE(commonPrefixLength, diff); |
| 175 return commonPrefixLength - diff; |
| 176 } |
| 177 |
| 178 // If current position is at grapheme boundary, return 0; otherwise, return the |
| 179 // distance to its nearest right grapheme boundary. |
| 180 static size_t computeDistanceToRightGraphemeBoundary(const Position& position) { |
| 181 const Position& adjustedPosition = nextPositionOf( |
| 182 previousPositionOf(position, PositionMoveType::GraphemeCluster), |
| 183 PositionMoveType::GraphemeCluster); |
| 184 DCHECK_EQ(position.anchorNode(), adjustedPosition.anchorNode()); |
| 185 DCHECK_GE(adjustedPosition.computeOffsetInContainerNode(), |
| 186 position.computeOffsetInContainerNode()); |
| 187 return static_cast<size_t>(adjustedPosition.computeOffsetInContainerNode() - |
| 188 position.computeOffsetInContainerNode()); |
| 189 } |
| 190 |
| 191 static size_t computeCommonGraphemeClusterSuffixLength( |
| 192 const String& oldText, |
| 193 const String& newText, |
| 194 const Element* rootEditableElement) { |
| 195 const size_t commonSuffixLength = computeCommonSuffixLength(oldText, newText); |
| 196 |
| 197 // For grapheme cluster, we should adjust it for grapheme boundary. |
| 198 const EphemeralRange& range = |
| 199 PlainTextRange(0, oldText.length() - commonSuffixLength) |
| 200 .createRange(*rootEditableElement); |
| 201 if (range.isNull()) |
| 202 return 0; |
| 203 const Position& position = range.endPosition(); |
| 204 const size_t diff = computeDistanceToRightGraphemeBoundary(position); |
| 205 DCHECK_GE(commonSuffixLength, diff); |
| 206 return commonSuffixLength - diff; |
| 207 } |
| 208 |
| 209 static PlainTextRange getSelectionOffsets(LocalFrame* frame) { |
| 210 EphemeralRange range = firstEphemeralRangeOf(frame->selection().selection()); |
| 211 if (range.isNull()) |
| 212 return PlainTextRange(); |
| 213 ContainerNode* editable = |
| 214 frame->selection().rootEditableElementOrTreeScopeRootNode(); |
| 215 DCHECK(editable); |
| 216 |
| 217 return PlainTextRange::create(*editable, range); |
| 218 } |
| 219 |
| 220 static const VisibleSelection createSelectionForIncrementalInsertion( |
| 221 const size_t start, |
| 222 const size_t end, |
| 223 const bool isDirectional, |
| 224 LocalFrame* frame) { |
| 225 Element* element = frame->selection().selection().rootEditableElement(); |
| 226 DCHECK(element); |
| 227 |
| 228 const EphemeralRange& startRange = |
| 229 PlainTextRange(0, static_cast<int>(start)).createRange(*element); |
| 230 DCHECK(startRange.isNotNull()); |
| 231 const Position& startPosition = startRange.endPosition(); |
| 232 |
| 233 const EphemeralRange& endRange = |
| 234 PlainTextRange(0, static_cast<int>(end)).createRange(*element); |
| 235 DCHECK(endRange.isNotNull()); |
| 236 const Position& endPosition = endRange.endPosition(); |
| 237 |
| 238 VisibleSelection selection = |
| 239 createVisibleSelection(SelectionInDOMTree::Builder() |
| 240 .setBaseAndExtent(startPosition, endPosition) |
| 241 .build()); |
| 242 selection.setIsDirectional(isDirectional); |
| 243 |
| 244 return selection; |
| 245 } |
| 246 |
| 247 void InsertIncrementalTextCommand::setSelection(const size_t start, |
| 248 const size_t end, |
| 249 LocalFrame* frame) { |
| 250 const VisibleSelection selection = createSelectionForIncrementalInsertion( |
| 251 start, end, endingSelection().isDirectional(), frame); |
| 252 setStartingSelection(selection); |
| 253 setEndingSelectionWithoutValidation(selection.start(), selection.end()); |
| 254 |
| 255 document().frame()->selection().setSelection(selection); |
| 256 } |
| 257 |
| 258 void InsertIncrementalTextCommand::doApply(EditingState* editingState) { |
| 259 // LOG(ERROR) << "hyb:\n"; |
| 260 // LOG(ERROR) << "hyb: InsertIncrementalTextCommand::doApply"; |
| 261 DCHECK_EQ(m_text.find('\n'), kNotFound); |
| 262 if (!endingSelection().isNonOrphanedCaretOrRange()) |
| 263 return; |
| 264 /* |
| 265 LOG(ERROR)<<"hyb: |
| 266 endingSelection().start()"<<endingSelection().start().computeOffsetInContainer
Node(); |
| 267 LOG(ERROR)<<"hyb: |
| 268 endingSelection().end()"<<endingSelection().end().computeOffsetInContainerNode
(); |
| 269 LOG(ERROR)<<"hyb: |
| 270 startingSelection().start()"<<startingSelection().start().computeOffsetInConta
inerNode(); |
| 271 LOG(ERROR)<<"hyb: |
| 272 startingSelection().end()"<<startingSelection().end().computeOffsetInContainer
Node(); |
| 273 LOG(ERROR)<<"hyb: frame |
| 274 start:"<<document().frame()->selection().selection().start().computeOffsetInCo
ntainerNode(); |
| 275 LOG(ERROR)<<"hyb: frame |
| 276 end:"<<document().frame()->selection().selection().end().computeOffsetInContai
nerNode(); |
| 277 LOG(ERROR)<<"hyb: m_text:"<<m_text.utf8().data(); |
| 278 |
| 279 LOG(ERROR)<<"hyb: endingSelection().end node()" |
| 280 <<endingSelection().end().computeContainerNode(); |
| 281 LOG(ERROR)<<"hyb: frame end node:"<< |
| 282 document().frame()->selection().selection().end().computeContainerNode(); |
| 283 |
| 284 */ |
| 285 |
| 286 LocalFrame* frame = document().frame(); |
| 287 DCHECK(frame); |
| 288 const Element* element = endingSelection().rootEditableElement(); |
| 289 DCHECK(element); |
| 290 |
| 291 const String& newText = m_text; |
| 292 String oldText = frame->selectedText(); |
| 293 // LOG(ERROR) << "hyb: -------oldText:" << oldText.utf8().data(); |
| 294 // LOG(ERROR) << "hyb: -------newText:" << newText.utf8().data(); |
| 295 |
| 296 //...........!!!!! |
| 297 // need some comment!!! |
| 298 if (element->tagName() == String("INPUT")) |
| 299 oldText = emptyString(); |
| 300 |
| 301 /* |
| 302 LOG(ERROR) << "hyb: " << |
| 303 element->getIdAttribute().getString().utf8().data(); |
| 304 LOG(ERROR) << "hyb: " |
| 305 << element->getNameAttribute().getString().utf8().data(); |
| 306 LOG(ERROR) << "hyb: " |
| 307 << element->getClassAttribute().getString().utf8().data(); |
| 308 |
| 309 LOG(ERROR) << "hyb: -------oldText:" << oldText.utf8().data(); |
| 310 */ |
| 311 |
| 312 const size_t newTextLength = newText.length(); |
| 313 const size_t commonPrefixLength = |
| 314 computeCommonGraphemeClusterPrefixLength(oldText, newText, element); |
| 315 // We should ignore common prefix when finding common suffix. |
| 316 const size_t commonSuffixLength = computeCommonGraphemeClusterSuffixLength( |
| 317 oldText.right(oldText.length() - commonPrefixLength), |
| 318 newText.right(newTextLength - commonPrefixLength), element); |
| 319 // LOG(ERROR)<<"hyb: common: "<<commonPrefixLength<<" "<<commonSuffixLength; |
| 320 |
| 321 const String textToInsert = |
| 322 newText.substring(commonPrefixLength, newTextLength - commonPrefixLength - |
| 323 commonSuffixLength); |
| 324 // LOG(ERROR) << "hyb: textToInsert:" << textToInsert.utf8().data(); |
| 325 |
| 326 PlainTextRange selectionOffsets = getSelectionOffsets(frame); |
| 327 const size_t selecitonStart = selectionOffsets.start(); |
| 328 const size_t selectionEnd = selectionOffsets.end(); |
| 329 const size_t insertionStart = selecitonStart + commonPrefixLength; |
| 330 const size_t insertionEnd = selectionEnd - commonSuffixLength; |
| 331 |
| 332 // LOG(ERROR) << "hyb: ..." << selecitonStart << " " << selectionEnd << " " |
| 333 //<< insertionStart << " " << insertionEnd; |
| 334 DCHECK_LE(insertionStart, insertionEnd); |
| 335 |
| 336 const VisibleSelection selectionForInsertion = |
| 337 createSelectionForIncrementalInsertion(insertionStart, insertionEnd, |
| 338 endingSelection().isDirectional(), |
| 339 frame); |
| 340 |
| 341 const bool changeSelection = selectionForInsertion != endingSelection(); |
| 342 // LOG(ERROR)<<"hyb: |
| 343 // selectionForInsertion().start()"<<selectionForInsertion.start().computeOffs
etInContainerNode(); |
| 344 // LOG(ERROR)<<"hyb: |
| 345 // selectionForInsertion().end()"<<selectionForInsertion.end().computeOffsetIn
ContainerNode(); |
| 346 |
| 347 setStartingSelection(selectionForInsertion); |
| 348 setEndingSelectionWithoutValidation(selectionForInsertion.start(), |
| 349 selectionForInsertion.end()); |
| 350 |
| 351 // ... |
| 352 // document().updateStyleAndLayoutIgnorePendingStylesheets(); // crash without |
| 353 // this line??? |
| 354 |
| 355 // Delete the current selection. |
| 356 // FIXME: This delete operation blows away the typing style. |
| 357 if (endingSelection().isRange()) { |
| 358 if (performTrivialReplace(textToInsert, m_selectInsertedText)) { |
| 359 // LOG(ERROR) << "hyb: 1.1"; |
| 360 if (changeSelection) |
| 361 setSelection(selecitonStart, selecitonStart + newTextLength, frame); |
| 362 document().updateStyleAndLayoutIgnorePendingStylesheets(); |
| 363 |
| 364 // document().updateStyleAndLayoutIgnorePendingStylesheets();//??? |
| 365 /* |
| 366 LOG(ERROR)<<"hyb: |
| 367 endingSelection().start()"<<endingSelection().start().computeOffsetInConta
inerNode(); |
| 368 LOG(ERROR)<<"hyb: |
| 369 endingSelection().end()"<<endingSelection().end().computeOffsetInContainer
Node(); |
| 370 |
| 371 PlainTextRange selectionOffsets = getSelectionOffsets(frame); |
| 372 const size_t selecitonStart = selectionOffsets.start(); |
| 373 const size_t selectionEnd = selectionOffsets.end(); |
| 374 LOG(ERROR)<<"hyb: ..."<<selecitonStart<<" "<<selectionEnd; |
| 375 |
| 376 LOG(ERROR)<<"hyb: frame end node:"<< |
| 377 document().frame()->selection().selection().end().computeContainerNode
(); |
| 378 */ |
| 379 return; |
| 380 } |
| 381 document().updateStyleAndLayoutIgnorePendingStylesheets(); |
| 382 bool endOfSelectionWasAtStartOfBlock = |
| 383 isStartOfBlock(endingSelection().visibleEnd()); |
| 384 deleteSelection(editingState, false, true, false, false); |
| 385 if (editingState->isAborted()) { |
| 386 // LOG(ERROR) << "hyb: 1.2"; |
| 387 return; |
| 388 } |
| 389 // deleteSelection eventually makes a new endingSelection out of a Position. |
| 390 // If that Position doesn't have a layoutObject (e.g. it is on a <frameset> |
| 391 // in the DOM), the VisibleSelection cannot be canonicalized to anything |
| 392 // other than NoSelection. The rest of this function requires a real |
| 393 // endingSelection, so bail out. |
| 394 if (endingSelection().isNone()) { |
| 395 // LOG(ERROR) << "hyb: 1.3"; |
| 396 return; |
| 397 } |
| 398 if (endOfSelectionWasAtStartOfBlock) { |
| 399 if (EditingStyle* typingStyle = frame->selection().typingStyle()) |
| 400 typingStyle->removeBlockProperties(); |
| 401 } |
| 402 } else if (frame->editor().isOverwriteModeEnabled()) { |
| 403 if (performOverwrite(textToInsert, m_selectInsertedText)) { |
| 404 if (changeSelection) |
| 405 setSelection(selecitonStart, selecitonStart + newTextLength, frame); |
| 406 document().updateStyleAndLayoutIgnorePendingStylesheets(); |
| 407 // LOG(ERROR) << "hyb: 1.4"; |
| 408 return; |
| 409 } |
| 410 } |
| 411 |
| 412 // LOG(ERROR) << "hyb: 222"; |
| 413 document().updateStyleAndLayoutIgnorePendingStylesheets(); |
| 414 |
| 415 Position startPosition(endingSelection().start()); |
| 416 |
| 417 Position placeholder; |
| 418 // We want to remove preserved newlines and brs that will collapse (and thus |
| 419 // become unnecessary) when content is inserted just before them. |
| 420 // FIXME: We shouldn't really have to do this, but removing placeholders is a |
| 421 // workaround for 9661. |
| 422 // If the caret is just before a placeholder, downstream will normalize the |
| 423 // caret to it. |
| 424 Position downstream(mostForwardCaretPosition(startPosition)); |
| 425 if (lineBreakExistsAtPosition(downstream)) { |
| 426 // FIXME: This doesn't handle placeholders at the end of anonymous blocks. |
| 427 VisiblePosition caret = createVisiblePosition(startPosition); |
| 428 if (isEndOfBlock(caret) && isStartOfParagraph(caret)) |
| 429 placeholder = downstream; |
| 430 // Don't remove the placeholder yet, otherwise the block we're inserting |
| 431 // into would collapse before we get a chance to insert into it. We check |
| 432 // for a placeholder now, though, because doing so requires the creation of |
| 433 // a VisiblePosition, and if we did that post-insertion it would force a |
| 434 // layout. |
| 435 } |
| 436 |
| 437 // Insert the character at the leftmost candidate. |
| 438 startPosition = mostBackwardCaretPosition(startPosition); |
| 439 |
| 440 // It is possible for the node that contains startPosition to contain only |
| 441 // unrendered whitespace, and so deleteInsignificantText could remove it. |
| 442 // Save the position before the node in case that happens. |
| 443 DCHECK(startPosition.computeContainerNode()) << startPosition; |
| 444 Position positionBeforeStartNode( |
| 445 Position::inParentBeforeNode(*startPosition.computeContainerNode())); |
| 446 deleteInsignificantText(startPosition, |
| 447 mostForwardCaretPosition(startPosition)); |
| 448 if (!startPosition.isConnected()) |
| 449 startPosition = positionBeforeStartNode; |
| 450 if (!isVisuallyEquivalentCandidate(startPosition)) |
| 451 startPosition = mostForwardCaretPosition(startPosition); |
| 452 |
| 453 startPosition = |
| 454 positionAvoidingSpecialElementBoundary(startPosition, editingState); |
| 455 if (editingState->isAborted()) |
| 456 return; |
| 457 |
| 458 // LOG(ERROR) << "hyb: 333"; |
| 459 Position endPosition; |
| 460 |
| 461 if (textToInsert == "\t" && isRichlyEditablePosition(startPosition)) { |
| 462 endPosition = insertTab(startPosition, editingState); |
| 463 if (editingState->isAborted()) |
| 464 return; |
| 465 startPosition = |
| 466 previousPositionOf(endPosition, PositionMoveType::GraphemeCluster); |
| 467 if (placeholder.isNotNull()) |
| 468 removePlaceholderAt(placeholder); |
| 469 } else { |
| 470 // Make sure the document is set up to receive textToInsert |
| 471 startPosition = positionInsideTextNode(startPosition, editingState); |
| 472 if (editingState->isAborted()) |
| 473 return; |
| 474 DCHECK(startPosition.isOffsetInAnchor()) << startPosition; |
| 475 DCHECK(startPosition.computeContainerNode()) << startPosition; |
| 476 DCHECK(startPosition.computeContainerNode()->isTextNode()) << startPosition; |
| 477 if (placeholder.isNotNull()) |
| 478 removePlaceholderAt(placeholder); |
| 479 Text* textNode = toText(startPosition.computeContainerNode()); |
| 480 const unsigned offset = startPosition.offsetInContainerNode(); |
| 481 |
| 482 insertTextIntoNode(textNode, offset, textToInsert); |
| 483 endPosition = Position(textNode, offset + textToInsert.length()); |
| 484 |
| 485 if (m_rebalanceType == RebalanceLeadingAndTrailingWhitespaces) { |
| 486 // The insertion may require adjusting adjacent whitespace, if it is |
| 487 // present. |
| 488 rebalanceWhitespaceAt(endPosition); |
| 489 // Rebalancing on both sides isn't necessary if we've inserted only |
| 490 // spaces. |
| 491 if (!shouldRebalanceLeadingWhitespaceFor(textToInsert)) |
| 492 rebalanceWhitespaceAt(startPosition); |
| 493 } else { |
| 494 DCHECK_EQ(m_rebalanceType, RebalanceAllWhitespaces); |
| 495 if (canRebalance(startPosition) && canRebalance(endPosition)) { |
| 496 rebalanceWhitespaceOnTextSubstring( |
| 497 textNode, startPosition.offsetInContainerNode(), |
| 498 endPosition.offsetInContainerNode()); |
| 499 } |
| 500 } |
| 501 } |
| 502 |
| 503 LOG(ERROR) << "hyb: 555"; |
| 504 document().updateStyleAndLayoutIgnorePendingStylesheets(); //?? |
| 505 |
| 506 LOG(ERROR) << "hyb: 666"; |
| 507 setEndingSelectionWithoutValidation(startPosition, endPosition); |
| 508 |
| 509 // document().updateStyleAndLayoutIgnorePendingStylesheets(); //??? |
| 510 |
| 511 /* |
| 512 LOG(ERROR) << "hyb: endingSelection().start()" |
| 513 << endingSelection().start().computeOffsetInContainerNode(); |
| 514 LOG(ERROR) << "hyb: endingSelection().end()" |
| 515 << endingSelection().end().computeOffsetInContainerNode(); |
| 516 */ |
| 517 |
| 518 // Handle the case where there is a typing style. |
| 519 if (EditingStyle* typingStyle = frame->selection().typingStyle()) { |
| 520 typingStyle->prepareToApplyAt(endPosition, |
| 521 EditingStyle::PreserveWritingDirection); |
| 522 if (!typingStyle->isEmpty()) { |
| 523 applyStyle(typingStyle, editingState); |
| 524 if (editingState->isAborted()) |
| 525 return; |
| 526 } |
| 527 } |
| 528 |
| 529 LOG(ERROR) << "hyb: 777"; /// |
| 530 // totally test... |
| 531 if (changeSelection) |
| 532 setSelection(selecitonStart, selecitonStart + newTextLength, frame); |
| 533 |
| 534 // document().updateStyleAndLayoutIgnorePendingStylesheets(); //??? |
| 535 |
| 536 if (!m_selectInsertedText) { |
| 537 SelectionInDOMTree::Builder builder; |
| 538 builder.setAffinity(endingSelection().affinity()); |
| 539 builder.setIsDirectional(endingSelection().isDirectional()); |
| 540 if (endingSelection().end().isNotNull()) |
| 541 builder.collapse(endingSelection().end()); |
| 542 setEndingSelection(builder.build()); |
| 543 } |
| 544 |
| 545 LOG(ERROR) << "hyb: 888"; //// |
| 546 } |
| 547 |
| 548 Position InsertIncrementalTextCommand::insertTab(const Position& pos, |
| 549 EditingState* editingState) { |
| 550 document().updateStyleAndLayoutIgnorePendingStylesheets(); |
| 551 |
| 552 Position insertPos = createVisiblePosition(pos).deepEquivalent(); |
| 553 if (insertPos.isNull()) |
| 554 return pos; |
| 555 |
| 556 Node* node = insertPos.computeContainerNode(); |
| 557 unsigned offset = node->isTextNode() ? insertPos.offsetInContainerNode() : 0; |
| 558 |
| 559 // keep tabs coalesced in tab span |
| 560 if (isTabHTMLSpanElementTextNode(node)) { |
| 561 Text* textNode = toText(node); |
| 562 insertTextIntoNode(textNode, offset, "\t"); |
| 563 return Position(textNode, offset + 1); |
| 564 } |
| 565 |
| 566 // create new tab span |
| 567 HTMLSpanElement* spanElement = createTabSpanElement(document()); |
| 568 |
| 569 // place it |
| 570 if (!node->isTextNode()) { |
| 571 insertNodeAt(spanElement, insertPos, editingState); |
| 572 } else { |
| 573 Text* textNode = toText(node); |
| 574 if (offset >= textNode->length()) { |
| 575 insertNodeAfter(spanElement, textNode, editingState); |
| 576 } else { |
| 577 // split node to make room for the span |
| 578 // NOTE: splitTextNode uses textNode for the |
| 579 // second node in the split, so we need to |
| 580 // insert the span before it. |
| 581 if (offset > 0) |
| 582 splitTextNode(textNode, offset); |
| 583 insertNodeBefore(spanElement, textNode, editingState); |
| 584 } |
| 585 } |
| 586 if (editingState->isAborted()) |
| 587 return Position(); |
| 588 |
| 589 // return the position following the new tab |
| 590 return Position::lastPositionInNode(spanElement); |
| 591 } |
| 592 |
| 593 } // namespace blink |
| OLD | NEW |