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 blink {
48
49 using namespace HTMLNames;
50
51 class TypingCommandLineOperation
52 {
53 public:
TypingCommandLineOperation(TypingCommand * typingCommand,bool selectInsertedText,const String & text)54 TypingCommandLineOperation(TypingCommand* typingCommand, bool selectInsertedText, const String& text)
55 : m_typingCommand(typingCommand)
56 , m_selectInsertedText(selectInsertedText)
57 , m_text(text)
58 { }
59
operator ()(size_t lineOffset,size_t lineLength,bool isLastLine) const60 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
TypingCommand(Document & document,ETypingCommand commandType,const String & textToInsert,Options options,TextGranularity granularity,TextCompositionType compositionType)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
deleteSelection(Document & document,Options options)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
deleteKeyPressed(Document & document,Options options,TextGranularity granularity)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
forwardDeleteKeyPressed(Document & document,Options options,TextGranularity granularity)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
updateSelectionIfDifferentFromCurrentSelection(TypingCommand * typingCommand,LocalFrame * frame)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
insertText(Document & document,const String & text,Options options,TextCompositionType composition)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.
insertText(Document & document,const String & text,const VisibleSelection & selectionForInsertion,Options options,TextCompositionType compositionType)170 void TypingCommand::insertText(Document& document, const String& text, const VisibleSelection& selectionForInsertion, Options options, TextCompositionType compositionType)
171 {
172 RefPtrWillBeRawPtr<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
insertLineBreak(Document & document,Options options)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
insertParagraphSeparatorInQuotedContent(Document & document)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
insertParagraphSeparator(Document & document,Options options)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
lastTypingCommandIfStillOpenForTyping(LocalFrame * frame)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
closeTyping(LocalFrame * frame)242 void TypingCommand::closeTyping(LocalFrame* frame)
243 {
244 if (RefPtrWillBeRawPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(frame))
245 lastTypingCommand->closeTyping();
246 }
247
doApply()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
editingAction() const284 EditAction TypingCommand::editingAction() const
285 {
286 return EditActionTyping;
287 }
288
markMisspellingsAfterTyping(ETypingCommand commandType)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
307 VisiblePosition p1 = startOfWord(previous, LeftWordIfOnBoundary);
308
309 if (commandType == InsertParagraphSeparator) {
310 VisiblePosition p2 = nextWordPosition(start);
311 VisibleSelection words(p1, endOfWord(p2));
312 frame->spellChecker().markMisspellingsAfterLineBreak(words);
313 } else if (previous.isNotNull()) {
314 VisiblePosition p2 = startOfWord(start, LeftWordIfOnBoundary);
315 if (p1 != p2)
316 frame->spellChecker().markMisspellingsAfterTypingToWord(p1, endingSelection());
317 }
318 }
319
typingAddedToOpenCommand(ETypingCommand commandTypeForAddedTyping)320 void TypingCommand::typingAddedToOpenCommand(ETypingCommand commandTypeForAddedTyping)
321 {
322 LocalFrame* frame = document().frame();
323 if (!frame)
324 return;
325
326 updatePreservesTypingStyle(commandTypeForAddedTyping);
327 updateCommandTypeOfOpenCommand(commandTypeForAddedTyping);
328
329 // The old spellchecking code requires that checking be done first, to prevent issues like that in 6864072, where <doesn't> is marked as misspelled.
330 markMisspellingsAfterTyping(commandTypeForAddedTyping);
331 frame->editor().appliedEditing(this);
332 }
333
insertText(const String & text,bool selectInsertedText)334 void TypingCommand::insertText(const String &text, bool selectInsertedText)
335 {
336 // FIXME: Need to implement selectInsertedText for cases where more than one insert is involved.
337 // This requires support from insertTextRunWithoutNewlines and insertParagraphSeparator for extending
338 // an existing selection; at the moment they can either put the caret after what's inserted or
339 // select what's inserted, but there's no way to "extend selection" to include both an old selection
340 // that ends just before where we want to insert text and the newly inserted text.
341 TypingCommandLineOperation operation(this, selectInsertedText, text);
342 forEachLineInString(text, operation);
343 }
344
insertTextRunWithoutNewlines(const String & text,bool selectInsertedText)345 void TypingCommand::insertTextRunWithoutNewlines(const String &text, bool selectInsertedText)
346 {
347 RefPtrWillBeRawPtr<InsertTextCommand> command = InsertTextCommand::create(document(), text, selectInsertedText,
348 m_compositionType == TextCompositionNone ? InsertTextCommand::RebalanceLeadingAndTrailingWhitespaces : InsertTextCommand::RebalanceAllWhitespaces);
349
350 applyCommandToComposite(command, endingSelection());
351
352 typingAddedToOpenCommand(InsertText);
353 }
354
insertLineBreak()355 void TypingCommand::insertLineBreak()
356 {
357 if (!canAppendNewLineFeedToSelection(endingSelection()))
358 return;
359
360 applyCommandToComposite(InsertLineBreakCommand::create(document()));
361 typingAddedToOpenCommand(InsertLineBreak);
362 }
363
insertParagraphSeparator()364 void TypingCommand::insertParagraphSeparator()
365 {
366 if (!canAppendNewLineFeedToSelection(endingSelection()))
367 return;
368
369 applyCommandToComposite(InsertParagraphSeparatorCommand::create(document()));
370 typingAddedToOpenCommand(InsertParagraphSeparator);
371 }
372
insertParagraphSeparatorInQuotedContent()373 void TypingCommand::insertParagraphSeparatorInQuotedContent()
374 {
375 // If the selection starts inside a table, just insert the paragraph separator normally
376 // Breaking the blockquote would also break apart the table, which is unecessary when inserting a newline
377 if (enclosingNodeOfType(endingSelection().start(), &isTableStructureNode)) {
378 insertParagraphSeparator();
379 return;
380 }
381
382 applyCommandToComposite(BreakBlockquoteCommand::create(document()));
383 typingAddedToOpenCommand(InsertParagraphSeparatorInQuotedContent);
384 }
385
makeEditableRootEmpty()386 bool TypingCommand::makeEditableRootEmpty()
387 {
388 Element* root = endingSelection().rootEditableElement();
389 if (!root || !root->hasChildren())
390 return false;
391
392 if (root->firstChild() == root->lastChild()) {
393 if (isHTMLBRElement(root->firstChild())) {
394 // If there is a single child and it could be a placeholder, leave it alone.
395 if (root->renderer() && root->renderer()->isRenderBlockFlow())
396 return false;
397 }
398 }
399
400 while (Node* child = root->firstChild())
401 removeNode(child);
402
403 addBlockPlaceholderIfNeeded(root);
404 setEndingSelection(VisibleSelection(firstPositionInNode(root), DOWNSTREAM, endingSelection().isDirectional()));
405
406 return true;
407 }
408
deleteKeyPressed(TextGranularity granularity,bool killRing)409 void TypingCommand::deleteKeyPressed(TextGranularity granularity, bool killRing)
410 {
411 LocalFrame* frame = document().frame();
412 if (!frame)
413 return;
414
415 frame->spellChecker().updateMarkersForWordsAffectedByEditing(false);
416
417 VisibleSelection selectionToDelete;
418 VisibleSelection selectionAfterUndo;
419
420 switch (endingSelection().selectionType()) {
421 case RangeSelection:
422 selectionToDelete = endingSelection();
423 selectionAfterUndo = selectionToDelete;
424 break;
425 case CaretSelection: {
426 // After breaking out of an empty mail blockquote, we still want continue with the deletion
427 // so actual content will get deleted, and not just the quote style.
428 if (breakOutOfEmptyMailBlockquotedParagraph())
429 typingAddedToOpenCommand(DeleteKey);
430
431 m_smartDelete = false;
432
433 OwnPtrWillBeRawPtr<FrameSelection> selection = FrameSelection::create();
434 selection->setSelection(endingSelection());
435 selection->modify(FrameSelection::AlterationExtend, DirectionBackward, granularity);
436 if (killRing && selection->isCaret() && granularity != CharacterGranularity)
437 selection->modify(FrameSelection::AlterationExtend, DirectionBackward, CharacterGranularity);
438
439 VisiblePosition visibleStart(endingSelection().visibleStart());
440 if (visibleStart.previous(CannotCrossEditingBoundary).isNull()) {
441 // When the caret is at the start of the editable area in an empty list item, break out of the list item.
442 if (breakOutOfEmptyListItem()) {
443 typingAddedToOpenCommand(DeleteKey);
444 return;
445 }
446 // When there are no visible positions in the editing root, delete its entire contents.
447 if (visibleStart.next(CannotCrossEditingBoundary).isNull() && makeEditableRootEmpty()) {
448 typingAddedToOpenCommand(DeleteKey);
449 return;
450 }
451 }
452
453 // If we have a caret selection at the beginning of a cell, we have nothing to do.
454 Node* enclosingTableCell = enclosingNodeOfType(visibleStart.deepEquivalent(), &isTableCell);
455 if (enclosingTableCell && visibleStart == VisiblePosition(firstPositionInNode(enclosingTableCell)))
456 return;
457
458 // If the caret is at the start of a paragraph after a table, move content into the last table cell.
459 if (isStartOfParagraph(visibleStart) && isFirstPositionAfterTable(visibleStart.previous(CannotCrossEditingBoundary))) {
460 // Unless the caret is just before a table. We don't want to move a table into the last table cell.
461 if (isLastPositionBeforeTable(visibleStart))
462 return;
463 // Extend the selection backward into the last cell, then deletion will handle the move.
464 selection->modify(FrameSelection::AlterationExtend, DirectionBackward, granularity);
465 // If the caret is just after a table, select the table and don't delete anything.
466 } else if (Element* table = isFirstPositionAfterTable(visibleStart)) {
467 setEndingSelection(VisibleSelection(positionBeforeNode(table), endingSelection().start(), DOWNSTREAM, endingSelection().isDirectional()));
468 typingAddedToOpenCommand(DeleteKey);
469 return;
470 }
471
472 selectionToDelete = selection->selection();
473
474 if (granularity == CharacterGranularity && selectionToDelete.end().containerNode() == selectionToDelete.start().containerNode()
475 && selectionToDelete.end().computeOffsetInContainerNode() - selectionToDelete.start().computeOffsetInContainerNode() > 1) {
476 // If there are multiple Unicode code points to be deleted, adjust the range to match platform conventions.
477 selectionToDelete.setWithoutValidation(selectionToDelete.end(), selectionToDelete.end().previous(BackwardDeletion));
478 }
479
480 if (!startingSelection().isRange() || selectionToDelete.base() != startingSelection().start())
481 selectionAfterUndo = selectionToDelete;
482 else
483 // It's a little tricky to compute what the starting selection would have been in the original document.
484 // We can't let the VisibleSelection class's validation kick in or it'll adjust for us based on
485 // the current state of the document and we'll get the wrong result.
486 selectionAfterUndo.setWithoutValidation(startingSelection().end(), selectionToDelete.extent());
487 break;
488 }
489 case NoSelection:
490 ASSERT_NOT_REACHED();
491 break;
492 }
493
494 ASSERT(!selectionToDelete.isNone());
495 if (selectionToDelete.isNone())
496 return;
497
498 if (selectionToDelete.isCaret())
499 return;
500
501 if (killRing)
502 frame->editor().addToKillRing(selectionToDelete.toNormalizedRange().get(), false);
503 // On Mac, make undo select everything that has been deleted, unless an undo will undo more than just this deletion.
504 // FIXME: This behaves like TextEdit except for the case where you open with text insertion and then delete
505 // more text than you insert. In that case all of the text that was around originally should be selected.
506 if (frame->editor().behavior().shouldUndoOfDeleteSelectText() && m_openedByBackwardDelete)
507 setStartingSelection(selectionAfterUndo);
508 CompositeEditCommand::deleteSelection(selectionToDelete, m_smartDelete);
509 setSmartDelete(false);
510 typingAddedToOpenCommand(DeleteKey);
511 }
512
forwardDeleteKeyPressed(TextGranularity granularity,bool killRing)513 void TypingCommand::forwardDeleteKeyPressed(TextGranularity granularity, bool killRing)
514 {
515 LocalFrame* frame = document().frame();
516 if (!frame)
517 return;
518
519 frame->spellChecker().updateMarkersForWordsAffectedByEditing(false);
520
521 VisibleSelection selectionToDelete;
522 VisibleSelection selectionAfterUndo;
523
524 switch (endingSelection().selectionType()) {
525 case RangeSelection:
526 selectionToDelete = endingSelection();
527 selectionAfterUndo = selectionToDelete;
528 break;
529 case CaretSelection: {
530 m_smartDelete = false;
531
532 // Handle delete at beginning-of-block case.
533 // Do nothing in the case that the caret is at the start of a
534 // root editable element or at the start of a document.
535 OwnPtrWillBeRawPtr<FrameSelection> selection = FrameSelection::create();
536 selection->setSelection(endingSelection());
537 selection->modify(FrameSelection::AlterationExtend, DirectionForward, granularity);
538 if (killRing && selection->isCaret() && granularity != CharacterGranularity)
539 selection->modify(FrameSelection::AlterationExtend, DirectionForward, CharacterGranularity);
540
541 Position downstreamEnd = endingSelection().end().downstream();
542 VisiblePosition visibleEnd = endingSelection().visibleEnd();
543 Node* enclosingTableCell = enclosingNodeOfType(visibleEnd.deepEquivalent(), &isTableCell);
544 if (enclosingTableCell && visibleEnd == VisiblePosition(lastPositionInNode(enclosingTableCell)))
545 return;
546 if (visibleEnd == endOfParagraph(visibleEnd))
547 downstreamEnd = visibleEnd.next(CannotCrossEditingBoundary).deepEquivalent().downstream();
548 // When deleting tables: Select the table first, then perform the deletion
549 if (isRenderedTableElement(downstreamEnd.containerNode()) && downstreamEnd.computeOffsetInContainerNode() <= caretMinOffset(downstreamEnd.containerNode())) {
550 setEndingSelection(VisibleSelection(endingSelection().end(), positionAfterNode(downstreamEnd.containerNode()), DOWNSTREAM, endingSelection().isDirectional()));
551 typingAddedToOpenCommand(ForwardDeleteKey);
552 return;
553 }
554
555 // deleting to end of paragraph when at end of paragraph needs to merge the next paragraph (if any)
556 if (granularity == ParagraphBoundary && selection->selection().isCaret() && isEndOfParagraph(selection->selection().visibleEnd()))
557 selection->modify(FrameSelection::AlterationExtend, DirectionForward, CharacterGranularity);
558
559 selectionToDelete = selection->selection();
560 if (!startingSelection().isRange() || selectionToDelete.base() != startingSelection().start())
561 selectionAfterUndo = selectionToDelete;
562 else {
563 // It's a little tricky to compute what the starting selection would have been in the original document.
564 // We can't let the VisibleSelection class's validation kick in or it'll adjust for us based on
565 // the current state of the document and we'll get the wrong result.
566 Position extent = startingSelection().end();
567 if (extent.containerNode() != selectionToDelete.end().containerNode())
568 extent = selectionToDelete.extent();
569 else {
570 int extraCharacters;
571 if (selectionToDelete.start().containerNode() == selectionToDelete.end().containerNode())
572 extraCharacters = selectionToDelete.end().computeOffsetInContainerNode() - selectionToDelete.start().computeOffsetInContainerNode();
573 else
574 extraCharacters = selectionToDelete.end().computeOffsetInContainerNode();
575 extent = Position(extent.containerNode(), extent.computeOffsetInContainerNode() + extraCharacters, Position::PositionIsOffsetInAnchor);
576 }
577 selectionAfterUndo.setWithoutValidation(startingSelection().start(), extent);
578 }
579 break;
580 }
581 case NoSelection:
582 ASSERT_NOT_REACHED();
583 break;
584 }
585
586 ASSERT(!selectionToDelete.isNone());
587 if (selectionToDelete.isNone())
588 return;
589
590 if (selectionToDelete.isCaret())
591 return;
592
593 if (killRing)
594 frame->editor().addToKillRing(selectionToDelete.toNormalizedRange().get(), false);
595 // Make undo select what was deleted on Mac alone
596 if (frame->editor().behavior().shouldUndoOfDeleteSelectText())
597 setStartingSelection(selectionAfterUndo);
598 CompositeEditCommand::deleteSelection(selectionToDelete, m_smartDelete);
599 setSmartDelete(false);
600 typingAddedToOpenCommand(ForwardDeleteKey);
601 }
602
deleteSelection(bool smartDelete)603 void TypingCommand::deleteSelection(bool smartDelete)
604 {
605 CompositeEditCommand::deleteSelection(smartDelete);
606 typingAddedToOpenCommand(DeleteSelection);
607 }
608
updatePreservesTypingStyle(ETypingCommand commandType)609 void TypingCommand::updatePreservesTypingStyle(ETypingCommand commandType)
610 {
611 switch (commandType) {
612 case DeleteSelection:
613 case DeleteKey:
614 case ForwardDeleteKey:
615 case InsertParagraphSeparator:
616 case InsertLineBreak:
617 m_preservesTypingStyle = true;
618 return;
619 case InsertParagraphSeparatorInQuotedContent:
620 case InsertText:
621 m_preservesTypingStyle = false;
622 return;
623 }
624 ASSERT_NOT_REACHED();
625 m_preservesTypingStyle = false;
626 }
627
isTypingCommand() const628 bool TypingCommand::isTypingCommand() const
629 {
630 return true;
631 }
632
633 } // namespace blink
634