• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "TypingCommand.h"
28 
29 #include "BeforeTextInsertedEvent.h"
30 #include "BreakBlockquoteCommand.h"
31 #include "DeleteSelectionCommand.h"
32 #include "Document.h"
33 #include "Editor.h"
34 #include "Element.h"
35 #include "Frame.h"
36 #include "HTMLNames.h"
37 #include "InsertLineBreakCommand.h"
38 #include "InsertParagraphSeparatorCommand.h"
39 #include "InsertTextCommand.h"
40 #include "RenderObject.h"
41 #include "SelectionController.h"
42 #include "TextIterator.h"
43 #include "VisiblePosition.h"
44 #include "htmlediting.h"
45 #include "visible_units.h"
46 
47 namespace WebCore {
48 
49 using namespace HTMLNames;
50 
canAppendNewLineFeed(const VisibleSelection & selection)51 static bool canAppendNewLineFeed(const VisibleSelection& selection)
52 {
53     Node* node = selection.rootEditableElement();
54     if (!node)
55         return false;
56 
57     RefPtr<BeforeTextInsertedEvent> event = BeforeTextInsertedEvent::create(String("\n"));
58     ExceptionCode ec = 0;
59     node->dispatchEvent(event, ec);
60     return event->text().length();
61 }
62 
TypingCommand(Document * document,ETypingCommand commandType,const String & textToInsert,Options options,TextGranularity granularity,TextCompositionType compositionType)63 TypingCommand::TypingCommand(Document *document, ETypingCommand commandType, const String &textToInsert, Options options, TextGranularity granularity, TextCompositionType compositionType)
64     : CompositeEditCommand(document)
65     , m_commandType(commandType)
66     , m_textToInsert(textToInsert)
67     , m_openForMoreTyping(true)
68     , m_selectInsertedText(options & SelectInsertedText)
69     , m_smartDelete(options & SmartDelete)
70     , m_granularity(granularity)
71     , m_compositionType(compositionType)
72     , m_killRing(options & KillRing)
73     , m_openedByBackwardDelete(false)
74     , m_shouldRetainAutocorrectionIndicator(options & RetainAutocorrectionIndicator)
75     , m_shouldPreventSpellChecking(options & PreventSpellChecking)
76 {
77     updatePreservesTypingStyle(m_commandType);
78 }
79 
deleteSelection(Document * document,Options options)80 void TypingCommand::deleteSelection(Document* document, Options options)
81 {
82     ASSERT(document);
83 
84     Frame* frame = document->frame();
85     ASSERT(frame);
86 
87     if (!frame->selection()->isRange())
88         return;
89 
90     EditCommand* lastEditCommand = frame->editor()->lastEditCommand();
91     if (isOpenForMoreTypingCommand(lastEditCommand)) {
92         TypingCommand* lastTypingCommand = static_cast<TypingCommand*>(lastEditCommand);
93         lastTypingCommand->setShouldPreventSpellChecking(options & PreventSpellChecking);
94         lastTypingCommand->deleteSelection(options & SmartDelete);
95         return;
96     }
97 
98     TypingCommand::create(document, DeleteSelection, "", options)->apply();
99 }
100 
deleteKeyPressed(Document * document,Options options,TextGranularity granularity)101 void TypingCommand::deleteKeyPressed(Document *document, Options options, TextGranularity granularity)
102 {
103     ASSERT(document);
104 
105     Frame* frame = document->frame();
106     ASSERT(frame);
107 
108     EditCommand* lastEditCommand = frame->editor()->lastEditCommand();
109     if (granularity == CharacterGranularity && isOpenForMoreTypingCommand(lastEditCommand)) {
110         TypingCommand* lastTypingCommand = static_cast<TypingCommand*>(lastEditCommand);
111         updateSelectionIfDifferentFromCurrentSelection(lastTypingCommand, frame);
112         lastTypingCommand->setShouldPreventSpellChecking(options & PreventSpellChecking);
113         lastTypingCommand->deleteKeyPressed(granularity, options & KillRing);
114         return;
115     }
116 
117     TypingCommand::create(document, DeleteKey, "", options, granularity)->apply();
118 }
119 
forwardDeleteKeyPressed(Document * document,Options options,TextGranularity granularity)120 void TypingCommand::forwardDeleteKeyPressed(Document *document, Options options, TextGranularity granularity)
121 {
122     // FIXME: Forward delete in TextEdit appears to open and close a new typing command.
123     ASSERT(document);
124 
125     Frame* frame = document->frame();
126     ASSERT(frame);
127 
128     EditCommand* lastEditCommand = frame->editor()->lastEditCommand();
129     if (granularity == CharacterGranularity && isOpenForMoreTypingCommand(lastEditCommand)) {
130         TypingCommand* lastTypingCommand = static_cast<TypingCommand*>(lastEditCommand);
131         updateSelectionIfDifferentFromCurrentSelection(lastTypingCommand, frame);
132         lastTypingCommand->setShouldPreventSpellChecking(options & PreventSpellChecking);
133         lastTypingCommand->forwardDeleteKeyPressed(granularity, options & KillRing);
134         return;
135     }
136 
137     TypingCommand::create(document, ForwardDeleteKey, "", options, granularity)->apply();
138 }
139 
updateSelectionIfDifferentFromCurrentSelection(TypingCommand * typingCommand,Frame * frame)140 void TypingCommand::updateSelectionIfDifferentFromCurrentSelection(TypingCommand* typingCommand, Frame* frame)
141 {
142     ASSERT(frame);
143     VisibleSelection currentSelection = frame->selection()->selection();
144     if (currentSelection == typingCommand->endingSelection())
145         return;
146 
147     typingCommand->setStartingSelection(currentSelection);
148     typingCommand->setEndingSelection(currentSelection);
149 }
150 
insertText(Document * document,const String & text,Options options,TextCompositionType composition)151 void TypingCommand::insertText(Document* document, const String& text, Options options, TextCompositionType composition)
152 {
153     ASSERT(document);
154 
155     Frame* frame = document->frame();
156     ASSERT(frame);
157 
158     if (!text.isEmpty())
159         document->frame()->editor()->updateMarkersForWordsAffectedByEditing(isSpaceOrNewline(text.characters()[0]));
160 
161     insertText(document, text, frame->selection()->selection(), options, composition);
162 }
163 
164 // FIXME: We shouldn't need to take selectionForInsertion. It should be identical to SelectionController's current selection.
insertText(Document * document,const String & text,const VisibleSelection & selectionForInsertion,Options options,TextCompositionType compositionType)165 void TypingCommand::insertText(Document* document, const String& text, const VisibleSelection& selectionForInsertion, Options options, TextCompositionType compositionType)
166 {
167     ASSERT(document);
168 
169     RefPtr<Frame> frame = document->frame();
170     ASSERT(frame);
171 
172     VisibleSelection currentSelection = frame->selection()->selection();
173     bool changeSelection = currentSelection != selectionForInsertion;
174     String newText = text;
175     Node* startNode = selectionForInsertion.start().deprecatedNode();
176 
177     if (startNode && startNode->rootEditableElement() && compositionType != TextCompositionUpdate) {
178         // Send BeforeTextInsertedEvent. The event handler will update text if necessary.
179         ExceptionCode ec = 0;
180         RefPtr<BeforeTextInsertedEvent> evt = BeforeTextInsertedEvent::create(text);
181         startNode->rootEditableElement()->dispatchEvent(evt, ec);
182         newText = evt->text();
183     }
184 
185     if (newText.isEmpty())
186         return;
187 
188     // Set the starting and ending selection appropriately if we are using a selection
189     // that is different from the current selection.  In the future, we should change EditCommand
190     // to deal with custom selections in a general way that can be used by all of the commands.
191     RefPtr<EditCommand> lastEditCommand = frame->editor()->lastEditCommand();
192     if (isOpenForMoreTypingCommand(lastEditCommand.get())) {
193         TypingCommand* lastTypingCommand = static_cast<TypingCommand*>(lastEditCommand.get());
194         if (lastTypingCommand->endingSelection() != selectionForInsertion) {
195             lastTypingCommand->setStartingSelection(selectionForInsertion);
196             lastTypingCommand->setEndingSelection(selectionForInsertion);
197         }
198 
199         lastTypingCommand->setCompositionType(compositionType);
200         lastTypingCommand->setShouldRetainAutocorrectionIndicator(options & RetainAutocorrectionIndicator);
201         lastTypingCommand->setShouldPreventSpellChecking(options & PreventSpellChecking);
202         lastTypingCommand->insertText(newText, options & SelectInsertedText);
203         return;
204     }
205 
206     RefPtr<TypingCommand> cmd = TypingCommand::create(document, InsertText, newText, options, compositionType);
207     if (changeSelection)  {
208         cmd->setStartingSelection(selectionForInsertion);
209         cmd->setEndingSelection(selectionForInsertion);
210     }
211     applyCommand(cmd);
212     if (changeSelection) {
213         cmd->setEndingSelection(currentSelection);
214         frame->selection()->setSelection(currentSelection);
215     }
216 }
217 
insertLineBreak(Document * document,Options options)218 void TypingCommand::insertLineBreak(Document *document, Options options)
219 {
220     ASSERT(document);
221 
222     Frame* frame = document->frame();
223     ASSERT(frame);
224 
225     EditCommand* lastEditCommand = frame->editor()->lastEditCommand();
226     if (isOpenForMoreTypingCommand(lastEditCommand)) {
227         TypingCommand* lastTypingCommand = static_cast<TypingCommand*>(lastEditCommand);
228         lastTypingCommand->setShouldRetainAutocorrectionIndicator(options & RetainAutocorrectionIndicator);
229         lastTypingCommand->insertLineBreak();
230         return;
231     }
232 
233     applyCommand(TypingCommand::create(document, InsertLineBreak, "", options));
234 }
235 
insertParagraphSeparatorInQuotedContent(Document * document)236 void TypingCommand::insertParagraphSeparatorInQuotedContent(Document *document)
237 {
238     ASSERT(document);
239 
240     Frame* frame = document->frame();
241     ASSERT(frame);
242 
243     EditCommand* lastEditCommand = frame->editor()->lastEditCommand();
244     if (isOpenForMoreTypingCommand(lastEditCommand)) {
245         static_cast<TypingCommand*>(lastEditCommand)->insertParagraphSeparatorInQuotedContent();
246         return;
247     }
248 
249     applyCommand(TypingCommand::create(document, InsertParagraphSeparatorInQuotedContent));
250 }
251 
insertParagraphSeparator(Document * document,Options options)252 void TypingCommand::insertParagraphSeparator(Document *document, Options options)
253 {
254     ASSERT(document);
255 
256     Frame* frame = document->frame();
257     ASSERT(frame);
258 
259     EditCommand* lastEditCommand = frame->editor()->lastEditCommand();
260     if (isOpenForMoreTypingCommand(lastEditCommand)) {
261         TypingCommand* lastTypingCommand = static_cast<TypingCommand*>(lastEditCommand);
262         lastTypingCommand->setShouldRetainAutocorrectionIndicator(options & RetainAutocorrectionIndicator);
263         lastTypingCommand->insertParagraphSeparator();
264         return;
265     }
266 
267     applyCommand(TypingCommand::create(document, InsertParagraphSeparator, "", options));
268 }
269 
isOpenForMoreTypingCommand(const EditCommand * cmd)270 bool TypingCommand::isOpenForMoreTypingCommand(const EditCommand* cmd)
271 {
272     return cmd && cmd->isTypingCommand() && static_cast<const TypingCommand*>(cmd)->isOpenForMoreTyping();
273 }
274 
closeTyping(EditCommand * cmd)275 void TypingCommand::closeTyping(EditCommand* cmd)
276 {
277     if (isOpenForMoreTypingCommand(cmd))
278         static_cast<TypingCommand*>(cmd)->closeTyping();
279 }
280 
doApply()281 void TypingCommand::doApply()
282 {
283     if (!endingSelection().isNonOrphanedCaretOrRange())
284         return;
285 
286     if (m_commandType == DeleteKey)
287         if (m_commands.isEmpty())
288             m_openedByBackwardDelete = true;
289 
290     switch (m_commandType) {
291     case DeleteSelection:
292         deleteSelection(m_smartDelete);
293         return;
294     case DeleteKey:
295         deleteKeyPressed(m_granularity, m_killRing);
296         return;
297     case ForwardDeleteKey:
298         forwardDeleteKeyPressed(m_granularity, m_killRing);
299         return;
300     case InsertLineBreak:
301         insertLineBreak();
302         return;
303     case InsertParagraphSeparator:
304         insertParagraphSeparator();
305         return;
306     case InsertParagraphSeparatorInQuotedContent:
307         insertParagraphSeparatorInQuotedContent();
308         return;
309     case InsertText:
310         insertText(m_textToInsert, m_selectInsertedText);
311         return;
312     }
313 
314     ASSERT_NOT_REACHED();
315 }
316 
editingAction() const317 EditAction TypingCommand::editingAction() const
318 {
319     return EditActionTyping;
320 }
321 
markMisspellingsAfterTyping(ETypingCommand commandType)322 void TypingCommand::markMisspellingsAfterTyping(ETypingCommand commandType)
323 {
324 #if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD)
325     if (!document()->frame()->editor()->isContinuousSpellCheckingEnabled()
326      && !document()->frame()->editor()->isAutomaticQuoteSubstitutionEnabled()
327      && !document()->frame()->editor()->isAutomaticLinkDetectionEnabled()
328      && !document()->frame()->editor()->isAutomaticDashSubstitutionEnabled()
329      && !document()->frame()->editor()->isAutomaticTextReplacementEnabled())
330         return;
331 #else
332     if (!document()->frame()->editor()->isContinuousSpellCheckingEnabled())
333         return;
334 #endif
335     // Take a look at the selection that results after typing and determine whether we need to spellcheck.
336     // Since the word containing the current selection is never marked, this does a check to
337     // see if typing made a new word that is not in the current selection. Basically, you
338     // get this by being at the end of a word and typing a space.
339     VisiblePosition start(endingSelection().start(), endingSelection().affinity());
340     VisiblePosition previous = start.previous();
341     if (previous.isNotNull()) {
342         VisiblePosition p1 = startOfWord(previous, LeftWordIfOnBoundary);
343         VisiblePosition p2 = startOfWord(start, LeftWordIfOnBoundary);
344         if (p1 != p2) {
345             RefPtr<Range> range = makeRange(p1, p2);
346             String strippedPreviousWord;
347             if (range && (commandType == TypingCommand::InsertText || commandType == TypingCommand::InsertLineBreak || commandType == TypingCommand::InsertParagraphSeparator || commandType == TypingCommand::InsertParagraphSeparatorInQuotedContent))
348                 strippedPreviousWord = plainText(range.get()).stripWhiteSpace();
349             document()->frame()->editor()->markMisspellingsAfterTypingToWord(p1, endingSelection(), !strippedPreviousWord.isEmpty());
350         } else if (commandType == TypingCommand::InsertText)
351             document()->frame()->editor()->startCorrectionPanelTimer();
352     }
353 }
354 
typingAddedToOpenCommand(ETypingCommand commandTypeForAddedTyping)355 void TypingCommand::typingAddedToOpenCommand(ETypingCommand commandTypeForAddedTyping)
356 {
357     updatePreservesTypingStyle(commandTypeForAddedTyping);
358 
359 #if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD)
360     document()->frame()->editor()->appliedEditing(this);
361     // Since the spellchecking code may also perform corrections and other replacements, it should happen after the typing changes.
362     if (!m_shouldPreventSpellChecking)
363         markMisspellingsAfterTyping(commandTypeForAddedTyping);
364 #else
365     // The old spellchecking code requires that checking be done first, to prevent issues like that in 6864072, where <doesn't> is marked as misspelled.
366     markMisspellingsAfterTyping(commandTypeForAddedTyping);
367     document()->frame()->editor()->appliedEditing(this);
368 #endif
369 }
370 
insertText(const String & text,bool selectInsertedText)371 void TypingCommand::insertText(const String &text, bool selectInsertedText)
372 {
373     // FIXME: Need to implement selectInsertedText for cases where more than one insert is involved.
374     // This requires support from insertTextRunWithoutNewlines and insertParagraphSeparator for extending
375     // an existing selection; at the moment they can either put the caret after what's inserted or
376     // select what's inserted, but there's no way to "extend selection" to include both an old selection
377     // that ends just before where we want to insert text and the newly inserted text.
378     unsigned offset = 0;
379     size_t newline;
380     while ((newline = text.find('\n', offset)) != notFound) {
381         if (newline != offset)
382             insertTextRunWithoutNewlines(text.substring(offset, newline - offset), false);
383         insertParagraphSeparator();
384         offset = newline + 1;
385     }
386     if (!offset)
387         insertTextRunWithoutNewlines(text, selectInsertedText);
388     else {
389         unsigned length = text.length();
390         if (length != offset)
391             insertTextRunWithoutNewlines(text.substring(offset, length - offset), selectInsertedText);
392     }
393 }
394 
insertTextRunWithoutNewlines(const String & text,bool selectInsertedText)395 void TypingCommand::insertTextRunWithoutNewlines(const String &text, bool selectInsertedText)
396 {
397     RefPtr<InsertTextCommand> command;
398     if (!document()->frame()->selection()->typingStyle() && !m_commands.isEmpty()) {
399         EditCommand* lastCommand = m_commands.last().get();
400         if (lastCommand->isInsertTextCommand())
401             command = static_cast<InsertTextCommand*>(lastCommand);
402     }
403     if (!command) {
404         command = InsertTextCommand::create(document());
405         applyCommandToComposite(command);
406     }
407     if (endingSelection() != command->endingSelection()) {
408         command->setStartingSelection(endingSelection());
409         command->setEndingSelection(endingSelection());
410     }
411     command->input(text, selectInsertedText,
412                    m_compositionType == TextCompositionNone ? InsertTextCommand::RebalanceLeadingAndTrailingWhitespaces : InsertTextCommand::RebalanceAllWhitespaces);
413     typingAddedToOpenCommand(InsertText);
414 }
415 
insertLineBreak()416 void TypingCommand::insertLineBreak()
417 {
418     if (!canAppendNewLineFeed(endingSelection()))
419         return;
420 
421     applyCommandToComposite(InsertLineBreakCommand::create(document()));
422     typingAddedToOpenCommand(InsertLineBreak);
423 }
424 
insertParagraphSeparator()425 void TypingCommand::insertParagraphSeparator()
426 {
427     if (!canAppendNewLineFeed(endingSelection()))
428         return;
429 
430     applyCommandToComposite(InsertParagraphSeparatorCommand::create(document()));
431     typingAddedToOpenCommand(InsertParagraphSeparator);
432 }
433 
insertParagraphSeparatorInQuotedContent()434 void TypingCommand::insertParagraphSeparatorInQuotedContent()
435 {
436     // If the selection starts inside a table, just insert the paragraph separator normally
437     // Breaking the blockquote would also break apart the table, which is unecessary when inserting a newline
438     if (enclosingNodeOfType(endingSelection().start(), &isTableStructureNode)) {
439         insertParagraphSeparator();
440         return;
441     }
442 
443     applyCommandToComposite(BreakBlockquoteCommand::create(document()));
444     typingAddedToOpenCommand(InsertParagraphSeparatorInQuotedContent);
445 }
446 
makeEditableRootEmpty()447 bool TypingCommand::makeEditableRootEmpty()
448 {
449     Element* root = endingSelection().rootEditableElement();
450     if (!root || !root->firstChild())
451         return false;
452 
453     if (root->firstChild() == root->lastChild() && root->firstElementChild() && root->firstElementChild()->hasTagName(brTag)) {
454         // If there is a single child and it could be a placeholder, leave it alone.
455         if (root->renderer() && root->renderer()->isBlockFlow())
456             return false;
457     }
458 
459     while (Node* child = root->firstChild())
460         removeNode(child);
461 
462     addBlockPlaceholderIfNeeded(root);
463     setEndingSelection(VisibleSelection(firstPositionInNode(root), DOWNSTREAM));
464 
465     return true;
466 }
467 
deleteKeyPressed(TextGranularity granularity,bool killRing)468 void TypingCommand::deleteKeyPressed(TextGranularity granularity, bool killRing)
469 {
470     document()->frame()->editor()->updateMarkersForWordsAffectedByEditing(false);
471 
472     VisibleSelection selectionToDelete;
473     VisibleSelection selectionAfterUndo;
474 
475     switch (endingSelection().selectionType()) {
476     case VisibleSelection::RangeSelection:
477         selectionToDelete = endingSelection();
478         selectionAfterUndo = selectionToDelete;
479         break;
480     case VisibleSelection::CaretSelection: {
481         // After breaking out of an empty mail blockquote, we still want continue with the deletion
482         // so actual content will get deleted, and not just the quote style.
483         if (breakOutOfEmptyMailBlockquotedParagraph())
484             typingAddedToOpenCommand(DeleteKey);
485 
486         m_smartDelete = false;
487 
488         SelectionController selection;
489         selection.setSelection(endingSelection());
490         selection.modify(SelectionController::AlterationExtend, DirectionBackward, granularity);
491         if (killRing && selection.isCaret() && granularity != CharacterGranularity)
492             selection.modify(SelectionController::AlterationExtend, DirectionBackward, CharacterGranularity);
493 
494         if (endingSelection().visibleStart().previous(CannotCrossEditingBoundary).isNull()) {
495             // When the caret is at the start of the editable area in an empty list item, break out of the list item.
496             if (breakOutOfEmptyListItem()) {
497                 typingAddedToOpenCommand(DeleteKey);
498                 return;
499             }
500             // When there are no visible positions in the editing root, delete its entire contents.
501             if (endingSelection().visibleStart().next(CannotCrossEditingBoundary).isNull() && makeEditableRootEmpty()) {
502                 typingAddedToOpenCommand(DeleteKey);
503                 return;
504             }
505         }
506 
507         VisiblePosition visibleStart(endingSelection().visibleStart());
508         // If we have a caret selection on an empty cell, we have nothing to do.
509         if (isEmptyTableCell(visibleStart.deepEquivalent().deprecatedNode()))
510             return;
511 
512         // If the caret is at the start of a paragraph after a table, move content into the last table cell.
513         if (isStartOfParagraph(visibleStart) && isFirstPositionAfterTable(visibleStart.previous(CannotCrossEditingBoundary))) {
514             // Unless the caret is just before a table.  We don't want to move a table into the last table cell.
515             if (isLastPositionBeforeTable(visibleStart))
516                 return;
517             // Extend the selection backward into the last cell, then deletion will handle the move.
518             selection.modify(SelectionController::AlterationExtend, DirectionBackward, granularity);
519         // If the caret is just after a table, select the table and don't delete anything.
520         } else if (Node* table = isFirstPositionAfterTable(visibleStart)) {
521             setEndingSelection(VisibleSelection(positionBeforeNode(table), endingSelection().start(), DOWNSTREAM));
522             typingAddedToOpenCommand(DeleteKey);
523             return;
524         }
525 
526         selectionToDelete = selection.selection();
527 
528         if (granularity == CharacterGranularity && selectionToDelete.end().deprecatedNode() == selectionToDelete.start().deprecatedNode() && selectionToDelete.end().deprecatedEditingOffset() - selectionToDelete.start().deprecatedEditingOffset() > 1) {
529             // If there are multiple Unicode code points to be deleted, adjust the range to match platform conventions.
530             selectionToDelete.setWithoutValidation(selectionToDelete.end(), selectionToDelete.end().previous(BackwardDeletion));
531         }
532 
533         if (!startingSelection().isRange() || selectionToDelete.base() != startingSelection().start())
534             selectionAfterUndo = selectionToDelete;
535         else
536             // It's a little tricky to compute what the starting selection would have been in the original document.
537             // We can't let the VisibleSelection class's validation kick in or it'll adjust for us based on
538             // the current state of the document and we'll get the wrong result.
539             selectionAfterUndo.setWithoutValidation(startingSelection().end(), selectionToDelete.extent());
540         break;
541     }
542     case VisibleSelection::NoSelection:
543         ASSERT_NOT_REACHED();
544         break;
545     }
546 
547     ASSERT(!selectionToDelete.isNone());
548     if (selectionToDelete.isNone())
549         return;
550 
551     if (selectionToDelete.isCaret() || !document()->frame()->selection()->shouldDeleteSelection(selectionToDelete))
552         return;
553 
554     if (killRing)
555         document()->frame()->editor()->addToKillRing(selectionToDelete.toNormalizedRange().get(), false);
556     // Make undo select everything that has been deleted, unless an undo will undo more than just this deletion.
557     // FIXME: This behaves like TextEdit except for the case where you open with text insertion and then delete
558     // more text than you insert.  In that case all of the text that was around originally should be selected.
559     if (m_openedByBackwardDelete)
560         setStartingSelection(selectionAfterUndo);
561     CompositeEditCommand::deleteSelection(selectionToDelete, m_smartDelete);
562     setSmartDelete(false);
563     typingAddedToOpenCommand(DeleteKey);
564 }
565 
forwardDeleteKeyPressed(TextGranularity granularity,bool killRing)566 void TypingCommand::forwardDeleteKeyPressed(TextGranularity granularity, bool killRing)
567 {
568     document()->frame()->editor()->updateMarkersForWordsAffectedByEditing(false);
569 
570     VisibleSelection selectionToDelete;
571     VisibleSelection selectionAfterUndo;
572 
573     switch (endingSelection().selectionType()) {
574     case VisibleSelection::RangeSelection:
575         selectionToDelete = endingSelection();
576         selectionAfterUndo = selectionToDelete;
577         break;
578     case VisibleSelection::CaretSelection: {
579         m_smartDelete = false;
580 
581         // Handle delete at beginning-of-block case.
582         // Do nothing in the case that the caret is at the start of a
583         // root editable element or at the start of a document.
584         SelectionController selection;
585         selection.setSelection(endingSelection());
586         selection.modify(SelectionController::AlterationExtend, DirectionForward, granularity);
587         if (killRing && selection.isCaret() && granularity != CharacterGranularity)
588             selection.modify(SelectionController::AlterationExtend, DirectionForward, CharacterGranularity);
589 
590         Position downstreamEnd = endingSelection().end().downstream();
591         VisiblePosition visibleEnd = endingSelection().visibleEnd();
592         if (visibleEnd == endOfParagraph(visibleEnd))
593             downstreamEnd = visibleEnd.next(CannotCrossEditingBoundary).deepEquivalent().downstream();
594         // When deleting tables: Select the table first, then perform the deletion
595         if (downstreamEnd.deprecatedNode() && downstreamEnd.deprecatedNode()->renderer() && downstreamEnd.deprecatedNode()->renderer()->isTable() && !downstreamEnd.deprecatedEditingOffset()) {
596             setEndingSelection(VisibleSelection(endingSelection().end(), positionAfterNode(downstreamEnd.deprecatedNode()), DOWNSTREAM));
597             typingAddedToOpenCommand(ForwardDeleteKey);
598             return;
599         }
600 
601         // deleting to end of paragraph when at end of paragraph needs to merge the next paragraph (if any)
602         if (granularity == ParagraphBoundary && selection.selection().isCaret() && isEndOfParagraph(selection.selection().visibleEnd()))
603             selection.modify(SelectionController::AlterationExtend, DirectionForward, CharacterGranularity);
604 
605         selectionToDelete = selection.selection();
606         if (!startingSelection().isRange() || selectionToDelete.base() != startingSelection().start())
607             selectionAfterUndo = selectionToDelete;
608         else {
609             // It's a little tricky to compute what the starting selection would have been in the original document.
610             // We can't let the VisibleSelection class's validation kick in or it'll adjust for us based on
611             // the current state of the document and we'll get the wrong result.
612             Position extent = startingSelection().end();
613             if (extent.deprecatedNode() != selectionToDelete.end().deprecatedNode())
614                 extent = selectionToDelete.extent();
615             else {
616                 int extraCharacters;
617                 if (selectionToDelete.start().deprecatedNode() == selectionToDelete.end().deprecatedNode())
618                     extraCharacters = selectionToDelete.end().deprecatedEditingOffset() - selectionToDelete.start().deprecatedEditingOffset();
619                 else
620                     extraCharacters = selectionToDelete.end().deprecatedEditingOffset();
621                 extent = Position(extent.deprecatedNode(), extent.deprecatedEditingOffset() + extraCharacters, Position::PositionIsOffsetInAnchor);
622             }
623             selectionAfterUndo.setWithoutValidation(startingSelection().start(), extent);
624         }
625         break;
626     }
627     case VisibleSelection::NoSelection:
628         ASSERT_NOT_REACHED();
629         break;
630     }
631 
632     ASSERT(!selectionToDelete.isNone());
633     if (selectionToDelete.isNone())
634         return;
635 
636     if (selectionToDelete.isCaret() || !document()->frame()->selection()->shouldDeleteSelection(selectionToDelete))
637         return;
638 
639     if (killRing)
640         document()->frame()->editor()->addToKillRing(selectionToDelete.toNormalizedRange().get(), false);
641     // make undo select what was deleted
642     setStartingSelection(selectionAfterUndo);
643     CompositeEditCommand::deleteSelection(selectionToDelete, m_smartDelete);
644     setSmartDelete(false);
645     typingAddedToOpenCommand(ForwardDeleteKey);
646 }
647 
deleteSelection(bool smartDelete)648 void TypingCommand::deleteSelection(bool smartDelete)
649 {
650     CompositeEditCommand::deleteSelection(smartDelete);
651     typingAddedToOpenCommand(DeleteSelection);
652 }
653 
updatePreservesTypingStyle(ETypingCommand commandType)654 void TypingCommand::updatePreservesTypingStyle(ETypingCommand commandType)
655 {
656     switch (commandType) {
657     case DeleteSelection:
658     case DeleteKey:
659     case ForwardDeleteKey:
660     case InsertParagraphSeparator:
661     case InsertLineBreak:
662         m_preservesTypingStyle = true;
663         return;
664     case InsertParagraphSeparatorInQuotedContent:
665     case InsertText:
666         m_preservesTypingStyle = false;
667         return;
668     }
669     ASSERT_NOT_REACHED();
670     m_preservesTypingStyle = false;
671 }
672 
isTypingCommand() const673 bool TypingCommand::isTypingCommand() const
674 {
675     return true;
676 }
677 
678 } // namespace WebCore
679