1 /* 2 * Copyright (C) 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY 14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR 17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26 #include "config.h" 27 #include "core/editing/TypingCommand.h" 28 29 #include "core/HTMLNames.h" 30 #include "core/dom/Document.h" 31 #include "core/dom/Element.h" 32 #include "core/dom/ElementTraversal.h" 33 #include "core/editing/BreakBlockquoteCommand.h" 34 #include "core/editing/Editor.h" 35 #include "core/editing/FrameSelection.h" 36 #include "core/editing/InsertLineBreakCommand.h" 37 #include "core/editing/InsertParagraphSeparatorCommand.h" 38 #include "core/editing/InsertTextCommand.h" 39 #include "core/editing/SpellChecker.h" 40 #include "core/editing/VisiblePosition.h" 41 #include "core/editing/VisibleUnits.h" 42 #include "core/editing/htmlediting.h" 43 #include "core/frame/LocalFrame.h" 44 #include "core/html/HTMLBRElement.h" 45 #include "core/rendering/RenderObject.h" 46 47 namespace WebCore { 48 49 using namespace HTMLNames; 50 51 class TypingCommandLineOperation 52 { 53 public: 54 TypingCommandLineOperation(TypingCommand* typingCommand, bool selectInsertedText, const String& text) 55 : m_typingCommand(typingCommand) 56 , m_selectInsertedText(selectInsertedText) 57 , m_text(text) 58 { } 59 60 void operator()(size_t lineOffset, size_t lineLength, bool isLastLine) const 61 { 62 if (isLastLine) { 63 if (!lineOffset || lineLength > 0) 64 m_typingCommand->insertTextRunWithoutNewlines(m_text.substring(lineOffset, lineLength), m_selectInsertedText); 65 } else { 66 if (lineLength > 0) 67 m_typingCommand->insertTextRunWithoutNewlines(m_text.substring(lineOffset, lineLength), false); 68 m_typingCommand->insertParagraphSeparator(); 69 } 70 } 71 72 private: 73 TypingCommand* m_typingCommand; 74 bool m_selectInsertedText; 75 const String& m_text; 76 }; 77 78 TypingCommand::TypingCommand(Document& document, ETypingCommand commandType, const String &textToInsert, Options options, TextGranularity granularity, TextCompositionType compositionType) 79 : TextInsertionBaseCommand(document) 80 , m_commandType(commandType) 81 , m_textToInsert(textToInsert) 82 , m_openForMoreTyping(true) 83 , m_selectInsertedText(options & SelectInsertedText) 84 , m_smartDelete(options & SmartDelete) 85 , m_granularity(granularity) 86 , m_compositionType(compositionType) 87 , m_killRing(options & KillRing) 88 , m_openedByBackwardDelete(false) 89 , m_shouldRetainAutocorrectionIndicator(options & RetainAutocorrectionIndicator) 90 , m_shouldPreventSpellChecking(options & PreventSpellChecking) 91 { 92 updatePreservesTypingStyle(m_commandType); 93 } 94 95 void TypingCommand::deleteSelection(Document& document, Options options) 96 { 97 LocalFrame* frame = document.frame(); 98 ASSERT(frame); 99 100 if (!frame->selection().isRange()) 101 return; 102 103 if (RefPtrWillBeRawPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(frame)) { 104 lastTypingCommand->setShouldPreventSpellChecking(options & PreventSpellChecking); 105 lastTypingCommand->deleteSelection(options & SmartDelete); 106 return; 107 } 108 109 TypingCommand::create(document, DeleteSelection, "", options)->apply(); 110 } 111 112 void TypingCommand::deleteKeyPressed(Document& document, Options options, TextGranularity granularity) 113 { 114 if (granularity == CharacterGranularity) { 115 LocalFrame* frame = document.frame(); 116 if (RefPtrWillBeRawPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(frame)) { 117 // If the last typing command is not Delete, open a new typing command. 118 // We need to group continuous delete commands alone in a single typing command. 119 if (lastTypingCommand->commandTypeOfOpenCommand() == DeleteKey) { 120 updateSelectionIfDifferentFromCurrentSelection(lastTypingCommand.get(), frame); 121 lastTypingCommand->setShouldPreventSpellChecking(options & PreventSpellChecking); 122 lastTypingCommand->deleteKeyPressed(granularity, options & KillRing); 123 return; 124 } 125 } 126 } 127 128 TypingCommand::create(document, DeleteKey, "", options, granularity)->apply(); 129 } 130 131 void TypingCommand::forwardDeleteKeyPressed(Document& document, Options options, TextGranularity granularity) 132 { 133 // FIXME: Forward delete in TextEdit appears to open and close a new typing command. 134 if (granularity == CharacterGranularity) { 135 LocalFrame* frame = document.frame(); 136 if (RefPtrWillBeRawPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(frame)) { 137 updateSelectionIfDifferentFromCurrentSelection(lastTypingCommand.get(), frame); 138 lastTypingCommand->setShouldPreventSpellChecking(options & PreventSpellChecking); 139 lastTypingCommand->forwardDeleteKeyPressed(granularity, options & KillRing); 140 return; 141 } 142 } 143 144 TypingCommand::create(document, ForwardDeleteKey, "", options, granularity)->apply(); 145 } 146 147 void TypingCommand::updateSelectionIfDifferentFromCurrentSelection(TypingCommand* typingCommand, LocalFrame* frame) 148 { 149 ASSERT(frame); 150 VisibleSelection currentSelection = frame->selection().selection(); 151 if (currentSelection == typingCommand->endingSelection()) 152 return; 153 154 typingCommand->setStartingSelection(currentSelection); 155 typingCommand->setEndingSelection(currentSelection); 156 } 157 158 void TypingCommand::insertText(Document& document, const String& text, Options options, TextCompositionType composition) 159 { 160 LocalFrame* frame = document.frame(); 161 ASSERT(frame); 162 163 if (!text.isEmpty()) 164 document.frame()->spellChecker().updateMarkersForWordsAffectedByEditing(isSpaceOrNewline(text[0])); 165 166 insertText(document, text, frame->selection().selection(), options, composition); 167 } 168 169 // FIXME: We shouldn't need to take selectionForInsertion. It should be identical to FrameSelection's current selection. 170 void TypingCommand::insertText(Document& document, const String& text, const VisibleSelection& selectionForInsertion, Options options, TextCompositionType compositionType) 171 { 172 RefPtr<LocalFrame> frame = document.frame(); 173 ASSERT(frame); 174 175 VisibleSelection currentSelection = frame->selection().selection(); 176 177 String newText = dispatchBeforeTextInsertedEvent(text, selectionForInsertion, compositionType == TextCompositionUpdate); 178 179 // Set the starting and ending selection appropriately if we are using a selection 180 // that is different from the current selection. In the future, we should change EditCommand 181 // to deal with custom selections in a general way that can be used by all of the commands. 182 if (RefPtrWillBeRawPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(frame.get())) { 183 if (lastTypingCommand->endingSelection() != selectionForInsertion) { 184 lastTypingCommand->setStartingSelection(selectionForInsertion); 185 lastTypingCommand->setEndingSelection(selectionForInsertion); 186 } 187 188 lastTypingCommand->setCompositionType(compositionType); 189 lastTypingCommand->setShouldRetainAutocorrectionIndicator(options & RetainAutocorrectionIndicator); 190 lastTypingCommand->setShouldPreventSpellChecking(options & PreventSpellChecking); 191 lastTypingCommand->insertText(newText, options & SelectInsertedText); 192 return; 193 } 194 195 RefPtrWillBeRawPtr<TypingCommand> cmd = TypingCommand::create(document, InsertText, newText, options, compositionType); 196 applyTextInsertionCommand(frame.get(), cmd, selectionForInsertion, currentSelection); 197 } 198 199 void TypingCommand::insertLineBreak(Document& document, Options options) 200 { 201 if (RefPtrWillBeRawPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(document.frame())) { 202 lastTypingCommand->setShouldRetainAutocorrectionIndicator(options & RetainAutocorrectionIndicator); 203 lastTypingCommand->insertLineBreak(); 204 return; 205 } 206 207 TypingCommand::create(document, InsertLineBreak, "", options)->apply(); 208 } 209 210 void TypingCommand::insertParagraphSeparatorInQuotedContent(Document& document) 211 { 212 if (RefPtrWillBeRawPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(document.frame())) { 213 lastTypingCommand->insertParagraphSeparatorInQuotedContent(); 214 return; 215 } 216 217 TypingCommand::create(document, InsertParagraphSeparatorInQuotedContent)->apply(); 218 } 219 220 void TypingCommand::insertParagraphSeparator(Document& document, Options options) 221 { 222 if (RefPtrWillBeRawPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(document.frame())) { 223 lastTypingCommand->setShouldRetainAutocorrectionIndicator(options & RetainAutocorrectionIndicator); 224 lastTypingCommand->insertParagraphSeparator(); 225 return; 226 } 227 228 TypingCommand::create(document, InsertParagraphSeparator, "", options)->apply(); 229 } 230 231 PassRefPtrWillBeRawPtr<TypingCommand> TypingCommand::lastTypingCommandIfStillOpenForTyping(LocalFrame* frame) 232 { 233 ASSERT(frame); 234 235 RefPtrWillBeRawPtr<CompositeEditCommand> lastEditCommand = frame->editor().lastEditCommand(); 236 if (!lastEditCommand || !lastEditCommand->isTypingCommand() || !static_cast<TypingCommand*>(lastEditCommand.get())->isOpenForMoreTyping()) 237 return nullptr; 238 239 return static_cast<TypingCommand*>(lastEditCommand.get()); 240 } 241 242 void TypingCommand::closeTyping(LocalFrame* frame) 243 { 244 if (RefPtrWillBeRawPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(frame)) 245 lastTypingCommand->closeTyping(); 246 } 247 248 void TypingCommand::doApply() 249 { 250 if (!endingSelection().isNonOrphanedCaretOrRange()) 251 return; 252 253 if (m_commandType == DeleteKey) 254 if (m_commands.isEmpty()) 255 m_openedByBackwardDelete = true; 256 257 switch (m_commandType) { 258 case DeleteSelection: 259 deleteSelection(m_smartDelete); 260 return; 261 case DeleteKey: 262 deleteKeyPressed(m_granularity, m_killRing); 263 return; 264 case ForwardDeleteKey: 265 forwardDeleteKeyPressed(m_granularity, m_killRing); 266 return; 267 case InsertLineBreak: 268 insertLineBreak(); 269 return; 270 case InsertParagraphSeparator: 271 insertParagraphSeparator(); 272 return; 273 case InsertParagraphSeparatorInQuotedContent: 274 insertParagraphSeparatorInQuotedContent(); 275 return; 276 case InsertText: 277 insertText(m_textToInsert, m_selectInsertedText); 278 return; 279 } 280 281 ASSERT_NOT_REACHED(); 282 } 283 284 EditAction TypingCommand::editingAction() const 285 { 286 return EditActionTyping; 287 } 288 289 void TypingCommand::markMisspellingsAfterTyping(ETypingCommand commandType) 290 { 291 LocalFrame* frame = document().frame(); 292 if (!frame) 293 return; 294 295 if (!frame->spellChecker().isContinuousSpellCheckingEnabled()) 296 return; 297 298 frame->spellChecker().cancelCheck(); 299 300 // Take a look at the selection that results after typing and determine whether we need to spellcheck. 301 // Since the word containing the current selection is never marked, this does a check to 302 // see if typing made a new word that is not in the current selection. Basically, you 303 // get this by being at the end of a word and typing a space. 304 VisiblePosition start(endingSelection().start(), endingSelection().affinity()); 305 VisiblePosition previous = start.previous(); 306 if (previous.isNotNull()) { 307 VisiblePosition p1 = startOfWord(previous, LeftWordIfOnBoundary); 308 VisiblePosition p2 = startOfWord(start, LeftWordIfOnBoundary); 309 if (p1 != p2) 310 frame->spellChecker().markMisspellingsAfterTypingToWord(p1, endingSelection()); 311 } 312 } 313 314 void TypingCommand::typingAddedToOpenCommand(ETypingCommand commandTypeForAddedTyping) 315 { 316 LocalFrame* frame = document().frame(); 317 if (!frame) 318 return; 319 320 updatePreservesTypingStyle(commandTypeForAddedTyping); 321 updateCommandTypeOfOpenCommand(commandTypeForAddedTyping); 322 323 // The old spellchecking code requires that checking be done first, to prevent issues like that in 6864072, where <doesn't> is marked as misspelled. 324 markMisspellingsAfterTyping(commandTypeForAddedTyping); 325 frame->editor().appliedEditing(this); 326 } 327 328 void TypingCommand::insertText(const String &text, bool selectInsertedText) 329 { 330 // FIXME: Need to implement selectInsertedText for cases where more than one insert is involved. 331 // This requires support from insertTextRunWithoutNewlines and insertParagraphSeparator for extending 332 // an existing selection; at the moment they can either put the caret after what's inserted or 333 // select what's inserted, but there's no way to "extend selection" to include both an old selection 334 // that ends just before where we want to insert text and the newly inserted text. 335 TypingCommandLineOperation operation(this, selectInsertedText, text); 336 forEachLineInString(text, operation); 337 } 338 339 void TypingCommand::insertTextRunWithoutNewlines(const String &text, bool selectInsertedText) 340 { 341 RefPtrWillBeRawPtr<InsertTextCommand> command = InsertTextCommand::create(document(), text, selectInsertedText, 342 m_compositionType == TextCompositionNone ? InsertTextCommand::RebalanceLeadingAndTrailingWhitespaces : InsertTextCommand::RebalanceAllWhitespaces); 343 344 applyCommandToComposite(command, endingSelection()); 345 346 typingAddedToOpenCommand(InsertText); 347 } 348 349 void TypingCommand::insertLineBreak() 350 { 351 if (!canAppendNewLineFeedToSelection(endingSelection())) 352 return; 353 354 applyCommandToComposite(InsertLineBreakCommand::create(document())); 355 typingAddedToOpenCommand(InsertLineBreak); 356 } 357 358 void TypingCommand::insertParagraphSeparator() 359 { 360 if (!canAppendNewLineFeedToSelection(endingSelection())) 361 return; 362 363 applyCommandToComposite(InsertParagraphSeparatorCommand::create(document())); 364 typingAddedToOpenCommand(InsertParagraphSeparator); 365 } 366 367 void TypingCommand::insertParagraphSeparatorInQuotedContent() 368 { 369 // If the selection starts inside a table, just insert the paragraph separator normally 370 // Breaking the blockquote would also break apart the table, which is unecessary when inserting a newline 371 if (enclosingNodeOfType(endingSelection().start(), &isTableStructureNode)) { 372 insertParagraphSeparator(); 373 return; 374 } 375 376 applyCommandToComposite(BreakBlockquoteCommand::create(document())); 377 typingAddedToOpenCommand(InsertParagraphSeparatorInQuotedContent); 378 } 379 380 bool TypingCommand::makeEditableRootEmpty() 381 { 382 Element* root = endingSelection().rootEditableElement(); 383 if (!root || !root->firstChild()) 384 return false; 385 386 if (root->firstChild() == root->lastChild()) { 387 Element* firstElementChild = ElementTraversal::firstWithin(*root); 388 if (isHTMLBRElement(firstElementChild)) { 389 // If there is a single child and it could be a placeholder, leave it alone. 390 if (root->renderer() && root->renderer()->isRenderBlockFlow()) 391 return false; 392 } 393 } 394 395 while (Node* child = root->firstChild()) 396 removeNode(child); 397 398 addBlockPlaceholderIfNeeded(root); 399 setEndingSelection(VisibleSelection(firstPositionInNode(root), DOWNSTREAM, endingSelection().isDirectional())); 400 401 return true; 402 } 403 404 void TypingCommand::deleteKeyPressed(TextGranularity granularity, bool killRing) 405 { 406 LocalFrame* frame = document().frame(); 407 if (!frame) 408 return; 409 410 frame->spellChecker().updateMarkersForWordsAffectedByEditing(false); 411 412 VisibleSelection selectionToDelete; 413 VisibleSelection selectionAfterUndo; 414 415 switch (endingSelection().selectionType()) { 416 case RangeSelection: 417 selectionToDelete = endingSelection(); 418 selectionAfterUndo = selectionToDelete; 419 break; 420 case CaretSelection: { 421 // After breaking out of an empty mail blockquote, we still want continue with the deletion 422 // so actual content will get deleted, and not just the quote style. 423 if (breakOutOfEmptyMailBlockquotedParagraph()) 424 typingAddedToOpenCommand(DeleteKey); 425 426 m_smartDelete = false; 427 428 OwnPtrWillBeRawPtr<FrameSelection> selection = FrameSelection::create(); 429 selection->setSelection(endingSelection()); 430 selection->modify(FrameSelection::AlterationExtend, DirectionBackward, granularity); 431 if (killRing && selection->isCaret() && granularity != CharacterGranularity) 432 selection->modify(FrameSelection::AlterationExtend, DirectionBackward, CharacterGranularity); 433 434 VisiblePosition visibleStart(endingSelection().visibleStart()); 435 if (visibleStart.previous(CannotCrossEditingBoundary).isNull()) { 436 // When the caret is at the start of the editable area in an empty list item, break out of the list item. 437 if (breakOutOfEmptyListItem()) { 438 typingAddedToOpenCommand(DeleteKey); 439 return; 440 } 441 // When there are no visible positions in the editing root, delete its entire contents. 442 if (visibleStart.next(CannotCrossEditingBoundary).isNull() && makeEditableRootEmpty()) { 443 typingAddedToOpenCommand(DeleteKey); 444 return; 445 } 446 } 447 448 // If we have a caret selection at the beginning of a cell, we have nothing to do. 449 Node* enclosingTableCell = enclosingNodeOfType(visibleStart.deepEquivalent(), &isTableCell); 450 if (enclosingTableCell && visibleStart == VisiblePosition(firstPositionInNode(enclosingTableCell))) 451 return; 452 453 // If the caret is at the start of a paragraph after a table, move content into the last table cell. 454 if (isStartOfParagraph(visibleStart) && isFirstPositionAfterTable(visibleStart.previous(CannotCrossEditingBoundary))) { 455 // Unless the caret is just before a table. We don't want to move a table into the last table cell. 456 if (isLastPositionBeforeTable(visibleStart)) 457 return; 458 // Extend the selection backward into the last cell, then deletion will handle the move. 459 selection->modify(FrameSelection::AlterationExtend, DirectionBackward, granularity); 460 // If the caret is just after a table, select the table and don't delete anything. 461 } else if (Node* table = isFirstPositionAfterTable(visibleStart)) { 462 setEndingSelection(VisibleSelection(positionBeforeNode(table), endingSelection().start(), DOWNSTREAM, endingSelection().isDirectional())); 463 typingAddedToOpenCommand(DeleteKey); 464 return; 465 } 466 467 selectionToDelete = selection->selection(); 468 469 if (granularity == CharacterGranularity && selectionToDelete.end().containerNode() == selectionToDelete.start().containerNode() 470 && selectionToDelete.end().computeOffsetInContainerNode() - selectionToDelete.start().computeOffsetInContainerNode() > 1) { 471 // If there are multiple Unicode code points to be deleted, adjust the range to match platform conventions. 472 selectionToDelete.setWithoutValidation(selectionToDelete.end(), selectionToDelete.end().previous(BackwardDeletion)); 473 } 474 475 if (!startingSelection().isRange() || selectionToDelete.base() != startingSelection().start()) 476 selectionAfterUndo = selectionToDelete; 477 else 478 // It's a little tricky to compute what the starting selection would have been in the original document. 479 // We can't let the VisibleSelection class's validation kick in or it'll adjust for us based on 480 // the current state of the document and we'll get the wrong result. 481 selectionAfterUndo.setWithoutValidation(startingSelection().end(), selectionToDelete.extent()); 482 break; 483 } 484 case NoSelection: 485 ASSERT_NOT_REACHED(); 486 break; 487 } 488 489 ASSERT(!selectionToDelete.isNone()); 490 if (selectionToDelete.isNone()) 491 return; 492 493 if (selectionToDelete.isCaret()) 494 return; 495 496 if (killRing) 497 frame->editor().addToKillRing(selectionToDelete.toNormalizedRange().get(), false); 498 // On Mac, make undo select everything that has been deleted, unless an undo will undo more than just this deletion. 499 // FIXME: This behaves like TextEdit except for the case where you open with text insertion and then delete 500 // more text than you insert. In that case all of the text that was around originally should be selected. 501 if (frame->editor().behavior().shouldUndoOfDeleteSelectText() && m_openedByBackwardDelete) 502 setStartingSelection(selectionAfterUndo); 503 CompositeEditCommand::deleteSelection(selectionToDelete, m_smartDelete); 504 setSmartDelete(false); 505 typingAddedToOpenCommand(DeleteKey); 506 } 507 508 void TypingCommand::forwardDeleteKeyPressed(TextGranularity granularity, bool killRing) 509 { 510 LocalFrame* frame = document().frame(); 511 if (!frame) 512 return; 513 514 frame->spellChecker().updateMarkersForWordsAffectedByEditing(false); 515 516 VisibleSelection selectionToDelete; 517 VisibleSelection selectionAfterUndo; 518 519 switch (endingSelection().selectionType()) { 520 case RangeSelection: 521 selectionToDelete = endingSelection(); 522 selectionAfterUndo = selectionToDelete; 523 break; 524 case CaretSelection: { 525 m_smartDelete = false; 526 527 // Handle delete at beginning-of-block case. 528 // Do nothing in the case that the caret is at the start of a 529 // root editable element or at the start of a document. 530 OwnPtrWillBeRawPtr<FrameSelection> selection = FrameSelection::create(); 531 selection->setSelection(endingSelection()); 532 selection->modify(FrameSelection::AlterationExtend, DirectionForward, granularity); 533 if (killRing && selection->isCaret() && granularity != CharacterGranularity) 534 selection->modify(FrameSelection::AlterationExtend, DirectionForward, CharacterGranularity); 535 536 Position downstreamEnd = endingSelection().end().downstream(); 537 VisiblePosition visibleEnd = endingSelection().visibleEnd(); 538 Node* enclosingTableCell = enclosingNodeOfType(visibleEnd.deepEquivalent(), &isTableCell); 539 if (enclosingTableCell && visibleEnd == VisiblePosition(lastPositionInNode(enclosingTableCell))) 540 return; 541 if (visibleEnd == endOfParagraph(visibleEnd)) 542 downstreamEnd = visibleEnd.next(CannotCrossEditingBoundary).deepEquivalent().downstream(); 543 // When deleting tables: Select the table first, then perform the deletion 544 if (isRenderedTable(downstreamEnd.containerNode()) && downstreamEnd.computeOffsetInContainerNode() <= caretMinOffset(downstreamEnd.containerNode())) { 545 setEndingSelection(VisibleSelection(endingSelection().end(), positionAfterNode(downstreamEnd.containerNode()), DOWNSTREAM, endingSelection().isDirectional())); 546 typingAddedToOpenCommand(ForwardDeleteKey); 547 return; 548 } 549 550 // deleting to end of paragraph when at end of paragraph needs to merge the next paragraph (if any) 551 if (granularity == ParagraphBoundary && selection->selection().isCaret() && isEndOfParagraph(selection->selection().visibleEnd())) 552 selection->modify(FrameSelection::AlterationExtend, DirectionForward, CharacterGranularity); 553 554 selectionToDelete = selection->selection(); 555 if (!startingSelection().isRange() || selectionToDelete.base() != startingSelection().start()) 556 selectionAfterUndo = selectionToDelete; 557 else { 558 // It's a little tricky to compute what the starting selection would have been in the original document. 559 // We can't let the VisibleSelection class's validation kick in or it'll adjust for us based on 560 // the current state of the document and we'll get the wrong result. 561 Position extent = startingSelection().end(); 562 if (extent.containerNode() != selectionToDelete.end().containerNode()) 563 extent = selectionToDelete.extent(); 564 else { 565 int extraCharacters; 566 if (selectionToDelete.start().containerNode() == selectionToDelete.end().containerNode()) 567 extraCharacters = selectionToDelete.end().computeOffsetInContainerNode() - selectionToDelete.start().computeOffsetInContainerNode(); 568 else 569 extraCharacters = selectionToDelete.end().computeOffsetInContainerNode(); 570 extent = Position(extent.containerNode(), extent.computeOffsetInContainerNode() + extraCharacters, Position::PositionIsOffsetInAnchor); 571 } 572 selectionAfterUndo.setWithoutValidation(startingSelection().start(), extent); 573 } 574 break; 575 } 576 case NoSelection: 577 ASSERT_NOT_REACHED(); 578 break; 579 } 580 581 ASSERT(!selectionToDelete.isNone()); 582 if (selectionToDelete.isNone()) 583 return; 584 585 if (selectionToDelete.isCaret()) 586 return; 587 588 if (killRing) 589 frame->editor().addToKillRing(selectionToDelete.toNormalizedRange().get(), false); 590 // Make undo select what was deleted on Mac alone 591 if (frame->editor().behavior().shouldUndoOfDeleteSelectText()) 592 setStartingSelection(selectionAfterUndo); 593 CompositeEditCommand::deleteSelection(selectionToDelete, m_smartDelete); 594 setSmartDelete(false); 595 typingAddedToOpenCommand(ForwardDeleteKey); 596 } 597 598 void TypingCommand::deleteSelection(bool smartDelete) 599 { 600 CompositeEditCommand::deleteSelection(smartDelete); 601 typingAddedToOpenCommand(DeleteSelection); 602 } 603 604 void TypingCommand::updatePreservesTypingStyle(ETypingCommand commandType) 605 { 606 switch (commandType) { 607 case DeleteSelection: 608 case DeleteKey: 609 case ForwardDeleteKey: 610 case InsertParagraphSeparator: 611 case InsertLineBreak: 612 m_preservesTypingStyle = true; 613 return; 614 case InsertParagraphSeparatorInQuotedContent: 615 case InsertText: 616 m_preservesTypingStyle = false; 617 return; 618 } 619 ASSERT_NOT_REACHED(); 620 m_preservesTypingStyle = false; 621 } 622 623 bool TypingCommand::isTypingCommand() const 624 { 625 return true; 626 } 627 628 } // namespace WebCore 629