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