2 * Copyright (C) 2005, 2006, 2007, 2008 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
27 #include "core/editing/TypingCommand.h"
29 #include "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/Frame.h"
44 #include "core/rendering/RenderObject.h"
48 using namespace HTMLNames;
50 class TypingCommandLineOperation
53 TypingCommandLineOperation(TypingCommand* typingCommand, bool selectInsertedText, const String& text)
54 : m_typingCommand(typingCommand)
55 , m_selectInsertedText(selectInsertedText)
59 void operator()(size_t lineOffset, size_t lineLength, bool isLastLine) const
62 if (!lineOffset || lineLength > 0)
63 m_typingCommand->insertTextRunWithoutNewlines(m_text.substring(lineOffset, lineLength), m_selectInsertedText);
66 m_typingCommand->insertTextRunWithoutNewlines(m_text.substring(lineOffset, lineLength), false);
67 m_typingCommand->insertParagraphSeparator();
72 TypingCommand* m_typingCommand;
73 bool m_selectInsertedText;
77 TypingCommand::TypingCommand(Document& document, ETypingCommand commandType, const String &textToInsert, Options options, TextGranularity granularity, TextCompositionType compositionType)
78 : TextInsertionBaseCommand(document)
79 , m_commandType(commandType)
80 , m_textToInsert(textToInsert)
81 , m_openForMoreTyping(true)
82 , m_selectInsertedText(options & SelectInsertedText)
83 , m_smartDelete(options & SmartDelete)
84 , m_granularity(granularity)
85 , m_compositionType(compositionType)
86 , m_killRing(options & KillRing)
87 , m_openedByBackwardDelete(false)
88 , m_shouldRetainAutocorrectionIndicator(options & RetainAutocorrectionIndicator)
89 , m_shouldPreventSpellChecking(options & PreventSpellChecking)
91 updatePreservesTypingStyle(m_commandType);
94 void TypingCommand::deleteSelection(Document& document, Options options)
96 Frame* frame = document.frame();
99 if (!frame->selection().isRange())
102 if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(frame)) {
103 lastTypingCommand->setShouldPreventSpellChecking(options & PreventSpellChecking);
104 lastTypingCommand->deleteSelection(options & SmartDelete);
108 TypingCommand::create(document, DeleteSelection, "", options)->apply();
111 void TypingCommand::deleteKeyPressed(Document& document, Options options, TextGranularity granularity)
113 if (granularity == CharacterGranularity) {
114 Frame* frame = document.frame();
115 if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(frame)) {
116 // If the last typing command is not Delete, open a new typing command.
117 // We need to group continuous delete commands alone in a single typing command.
118 if (lastTypingCommand->commandTypeOfOpenCommand() == DeleteKey) {
119 updateSelectionIfDifferentFromCurrentSelection(lastTypingCommand.get(), frame);
120 lastTypingCommand->setShouldPreventSpellChecking(options & PreventSpellChecking);
121 lastTypingCommand->deleteKeyPressed(granularity, options & KillRing);
127 TypingCommand::create(document, DeleteKey, "", options, granularity)->apply();
130 void TypingCommand::forwardDeleteKeyPressed(Document& document, Options options, TextGranularity granularity)
132 // FIXME: Forward delete in TextEdit appears to open and close a new typing command.
133 if (granularity == CharacterGranularity) {
134 Frame* frame = document.frame();
135 if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(frame)) {
136 updateSelectionIfDifferentFromCurrentSelection(lastTypingCommand.get(), frame);
137 lastTypingCommand->setShouldPreventSpellChecking(options & PreventSpellChecking);
138 lastTypingCommand->forwardDeleteKeyPressed(granularity, options & KillRing);
143 TypingCommand::create(document, ForwardDeleteKey, "", options, granularity)->apply();
146 void TypingCommand::updateSelectionIfDifferentFromCurrentSelection(TypingCommand* typingCommand, Frame* frame)
149 VisibleSelection currentSelection = frame->selection().selection();
150 if (currentSelection == typingCommand->endingSelection())
153 typingCommand->setStartingSelection(currentSelection);
154 typingCommand->setEndingSelection(currentSelection);
157 void TypingCommand::insertText(Document& document, const String& text, Options options, TextCompositionType composition)
159 Frame* frame = document.frame();
163 document.frame()->spellChecker().updateMarkersForWordsAffectedByEditing(isSpaceOrNewline(text[0]));
165 insertText(document, text, frame->selection().selection(), options, composition);
168 // FIXME: We shouldn't need to take selectionForInsertion. It should be identical to FrameSelection's current selection.
169 void TypingCommand::insertText(Document& document, const String& text, const VisibleSelection& selectionForInsertion, Options options, TextCompositionType compositionType)
171 RefPtr<Frame> frame = document.frame();
174 VisibleSelection currentSelection = frame->selection().selection();
176 String newText = dispatchBeforeTextInsertedEvent(text, selectionForInsertion, compositionType == TextCompositionUpdate);
178 // Set the starting and ending selection appropriately if we are using a selection
179 // that is different from the current selection. In the future, we should change EditCommand
180 // to deal with custom selections in a general way that can be used by all of the commands.
181 if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(frame.get())) {
182 if (lastTypingCommand->endingSelection() != selectionForInsertion) {
183 lastTypingCommand->setStartingSelection(selectionForInsertion);
184 lastTypingCommand->setEndingSelection(selectionForInsertion);
187 lastTypingCommand->setCompositionType(compositionType);
188 lastTypingCommand->setShouldRetainAutocorrectionIndicator(options & RetainAutocorrectionIndicator);
189 lastTypingCommand->setShouldPreventSpellChecking(options & PreventSpellChecking);
190 lastTypingCommand->insertText(newText, options & SelectInsertedText);
194 RefPtr<TypingCommand> cmd = TypingCommand::create(document, InsertText, newText, options, compositionType);
195 applyTextInsertionCommand(frame.get(), cmd, selectionForInsertion, currentSelection);
198 void TypingCommand::insertLineBreak(Document& document, Options options)
200 if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(document.frame())) {
201 lastTypingCommand->setShouldRetainAutocorrectionIndicator(options & RetainAutocorrectionIndicator);
202 lastTypingCommand->insertLineBreak();
206 TypingCommand::create(document, InsertLineBreak, "", options)->apply();
209 void TypingCommand::insertParagraphSeparatorInQuotedContent(Document& document)
211 if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(document.frame())) {
212 lastTypingCommand->insertParagraphSeparatorInQuotedContent();
216 TypingCommand::create(document, InsertParagraphSeparatorInQuotedContent)->apply();
219 void TypingCommand::insertParagraphSeparator(Document& document, Options options)
221 if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(document.frame())) {
222 lastTypingCommand->setShouldRetainAutocorrectionIndicator(options & RetainAutocorrectionIndicator);
223 lastTypingCommand->insertParagraphSeparator();
227 TypingCommand::create(document, InsertParagraphSeparator, "", options)->apply();
230 PassRefPtr<TypingCommand> TypingCommand::lastTypingCommandIfStillOpenForTyping(Frame* frame)
234 RefPtr<CompositeEditCommand> lastEditCommand = frame->editor().lastEditCommand();
235 if (!lastEditCommand || !lastEditCommand->isTypingCommand() || !static_cast<TypingCommand*>(lastEditCommand.get())->isOpenForMoreTyping())
238 return static_cast<TypingCommand*>(lastEditCommand.get());
241 void TypingCommand::closeTyping(Frame* frame)
243 if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(frame))
244 lastTypingCommand->closeTyping();
247 void TypingCommand::doApply()
249 if (!endingSelection().isNonOrphanedCaretOrRange())
252 if (m_commandType == DeleteKey)
253 if (m_commands.isEmpty())
254 m_openedByBackwardDelete = true;
256 switch (m_commandType) {
257 case DeleteSelection:
258 deleteSelection(m_smartDelete);
261 deleteKeyPressed(m_granularity, m_killRing);
263 case ForwardDeleteKey:
264 forwardDeleteKeyPressed(m_granularity, m_killRing);
266 case InsertLineBreak:
269 case InsertParagraphSeparator:
270 insertParagraphSeparator();
272 case InsertParagraphSeparatorInQuotedContent:
273 insertParagraphSeparatorInQuotedContent();
276 insertText(m_textToInsert, m_selectInsertedText);
280 ASSERT_NOT_REACHED();
283 EditAction TypingCommand::editingAction() const
285 return EditActionTyping;
288 void TypingCommand::markMisspellingsAfterTyping(ETypingCommand commandType)
290 Frame* frame = document().frame();
294 if (!frame->spellChecker().isContinuousSpellCheckingEnabled())
297 frame->spellChecker().cancelCheck();
299 // Take a look at the selection that results after typing and determine whether we need to spellcheck.
300 // Since the word containing the current selection is never marked, this does a check to
301 // see if typing made a new word that is not in the current selection. Basically, you
302 // get this by being at the end of a word and typing a space.
303 VisiblePosition start(endingSelection().start(), endingSelection().affinity());
304 VisiblePosition previous = start.previous();
305 if (previous.isNotNull()) {
306 VisiblePosition p1 = startOfWord(previous, LeftWordIfOnBoundary);
307 VisiblePosition p2 = startOfWord(start, LeftWordIfOnBoundary);
309 frame->spellChecker().markMisspellingsAfterTypingToWord(p1, endingSelection());
313 void TypingCommand::typingAddedToOpenCommand(ETypingCommand commandTypeForAddedTyping)
315 Frame* frame = document().frame();
319 updatePreservesTypingStyle(commandTypeForAddedTyping);
320 updateCommandTypeOfOpenCommand(commandTypeForAddedTyping);
322 // The old spellchecking code requires that checking be done first, to prevent issues like that in 6864072, where <doesn't> is marked as misspelled.
323 markMisspellingsAfterTyping(commandTypeForAddedTyping);
324 frame->editor().appliedEditing(this);
327 void TypingCommand::insertText(const String &text, bool selectInsertedText)
329 // FIXME: Need to implement selectInsertedText for cases where more than one insert is involved.
330 // This requires support from insertTextRunWithoutNewlines and insertParagraphSeparator for extending
331 // an existing selection; at the moment they can either put the caret after what's inserted or
332 // select what's inserted, but there's no way to "extend selection" to include both an old selection
333 // that ends just before where we want to insert text and the newly inserted text.
334 TypingCommandLineOperation operation(this, selectInsertedText, text);
335 forEachLineInString(text, operation);
338 void TypingCommand::insertTextRunWithoutNewlines(const String &text, bool selectInsertedText)
340 RefPtr<InsertTextCommand> command = InsertTextCommand::create(document(), text, selectInsertedText,
341 m_compositionType == TextCompositionNone ? InsertTextCommand::RebalanceLeadingAndTrailingWhitespaces : InsertTextCommand::RebalanceAllWhitespaces);
343 applyCommandToComposite(command, endingSelection());
345 typingAddedToOpenCommand(InsertText);
348 void TypingCommand::insertLineBreak()
350 if (!canAppendNewLineFeedToSelection(endingSelection()))
353 applyCommandToComposite(InsertLineBreakCommand::create(document()));
354 typingAddedToOpenCommand(InsertLineBreak);
357 void TypingCommand::insertParagraphSeparator()
359 if (!canAppendNewLineFeedToSelection(endingSelection()))
362 applyCommandToComposite(InsertParagraphSeparatorCommand::create(document()));
363 typingAddedToOpenCommand(InsertParagraphSeparator);
366 void TypingCommand::insertParagraphSeparatorInQuotedContent()
368 // If the selection starts inside a table, just insert the paragraph separator normally
369 // Breaking the blockquote would also break apart the table, which is unecessary when inserting a newline
370 if (enclosingNodeOfType(endingSelection().start(), &isTableStructureNode)) {
371 insertParagraphSeparator();
375 applyCommandToComposite(BreakBlockquoteCommand::create(document()));
376 typingAddedToOpenCommand(InsertParagraphSeparatorInQuotedContent);
379 bool TypingCommand::makeEditableRootEmpty()
381 Element* root = endingSelection().rootEditableElement();
382 if (!root || !root->firstChild())
385 if (root->firstChild() == root->lastChild()) {
386 Element* firstElementChild = ElementTraversal::firstWithin(*root);
387 if (firstElementChild && firstElementChild->hasTagName(brTag)) {
388 // If there is a single child and it could be a placeholder, leave it alone.
389 if (root->renderer() && root->renderer()->isRenderBlockFlow())
394 while (Node* child = root->firstChild())
397 addBlockPlaceholderIfNeeded(root);
398 setEndingSelection(VisibleSelection(firstPositionInNode(root), DOWNSTREAM, endingSelection().isDirectional()));
403 void TypingCommand::deleteKeyPressed(TextGranularity granularity, bool killRing)
405 Frame* frame = document().frame();
409 frame->spellChecker().updateMarkersForWordsAffectedByEditing(false);
411 VisibleSelection selectionToDelete;
412 VisibleSelection selectionAfterUndo;
414 switch (endingSelection().selectionType()) {
416 selectionToDelete = endingSelection();
417 selectionAfterUndo = selectionToDelete;
419 case CaretSelection: {
420 // After breaking out of an empty mail blockquote, we still want continue with the deletion
421 // so actual content will get deleted, and not just the quote style.
422 if (breakOutOfEmptyMailBlockquotedParagraph())
423 typingAddedToOpenCommand(DeleteKey);
425 m_smartDelete = false;
427 FrameSelection selection;
428 selection.setSelection(endingSelection());
429 selection.modify(FrameSelection::AlterationExtend, DirectionBackward, granularity);
430 if (killRing && selection.isCaret() && granularity != CharacterGranularity)
431 selection.modify(FrameSelection::AlterationExtend, DirectionBackward, CharacterGranularity);
433 VisiblePosition visibleStart(endingSelection().visibleStart());
434 if (visibleStart.previous(CannotCrossEditingBoundary).isNull()) {
435 // When the caret is at the start of the editable area in an empty list item, break out of the list item.
436 if (breakOutOfEmptyListItem()) {
437 typingAddedToOpenCommand(DeleteKey);
440 // When there are no visible positions in the editing root, delete its entire contents.
441 if (visibleStart.next(CannotCrossEditingBoundary).isNull() && makeEditableRootEmpty()) {
442 typingAddedToOpenCommand(DeleteKey);
447 // If we have a caret selection at the beginning of a cell, we have nothing to do.
448 Node* enclosingTableCell = enclosingNodeOfType(visibleStart.deepEquivalent(), &isTableCell);
449 if (enclosingTableCell && visibleStart == firstPositionInNode(enclosingTableCell))
452 // If the caret is at the start of a paragraph after a table, move content into the last table cell.
453 if (isStartOfParagraph(visibleStart) && isFirstPositionAfterTable(visibleStart.previous(CannotCrossEditingBoundary))) {
454 // Unless the caret is just before a table. We don't want to move a table into the last table cell.
455 if (isLastPositionBeforeTable(visibleStart))
457 // Extend the selection backward into the last cell, then deletion will handle the move.
458 selection.modify(FrameSelection::AlterationExtend, DirectionBackward, granularity);
459 // If the caret is just after a table, select the table and don't delete anything.
460 } else if (Node* table = isFirstPositionAfterTable(visibleStart)) {
461 setEndingSelection(VisibleSelection(positionBeforeNode(table), endingSelection().start(), DOWNSTREAM, endingSelection().isDirectional()));
462 typingAddedToOpenCommand(DeleteKey);
466 selectionToDelete = selection.selection();
468 if (granularity == CharacterGranularity && selectionToDelete.end().containerNode() == selectionToDelete.start().containerNode()
469 && selectionToDelete.end().computeOffsetInContainerNode() - selectionToDelete.start().computeOffsetInContainerNode() > 1) {
470 // If there are multiple Unicode code points to be deleted, adjust the range to match platform conventions.
471 selectionToDelete.setWithoutValidation(selectionToDelete.end(), selectionToDelete.end().previous(BackwardDeletion));
474 if (!startingSelection().isRange() || selectionToDelete.base() != startingSelection().start())
475 selectionAfterUndo = selectionToDelete;
477 // It's a little tricky to compute what the starting selection would have been in the original document.
478 // We can't let the VisibleSelection class's validation kick in or it'll adjust for us based on
479 // the current state of the document and we'll get the wrong result.
480 selectionAfterUndo.setWithoutValidation(startingSelection().end(), selectionToDelete.extent());
484 ASSERT_NOT_REACHED();
488 ASSERT(!selectionToDelete.isNone());
489 if (selectionToDelete.isNone())
492 if (selectionToDelete.isCaret())
496 frame->editor().addToKillRing(selectionToDelete.toNormalizedRange().get(), false);
497 // On Mac, make undo select everything that has been deleted, unless an undo will undo more than just this deletion.
498 // FIXME: This behaves like TextEdit except for the case where you open with text insertion and then delete
499 // more text than you insert. In that case all of the text that was around originally should be selected.
500 if (frame->editor().behavior().shouldUndoOfDeleteSelectText() && m_openedByBackwardDelete)
501 setStartingSelection(selectionAfterUndo);
502 CompositeEditCommand::deleteSelection(selectionToDelete, m_smartDelete);
503 setSmartDelete(false);
504 typingAddedToOpenCommand(DeleteKey);
507 void TypingCommand::forwardDeleteKeyPressed(TextGranularity granularity, bool killRing)
509 Frame* frame = document().frame();
513 frame->spellChecker().updateMarkersForWordsAffectedByEditing(false);
515 VisibleSelection selectionToDelete;
516 VisibleSelection selectionAfterUndo;
518 switch (endingSelection().selectionType()) {
520 selectionToDelete = endingSelection();
521 selectionAfterUndo = selectionToDelete;
523 case CaretSelection: {
524 m_smartDelete = false;
526 // Handle delete at beginning-of-block case.
527 // Do nothing in the case that the caret is at the start of a
528 // root editable element or at the start of a document.
529 FrameSelection selection;
530 selection.setSelection(endingSelection());
531 selection.modify(FrameSelection::AlterationExtend, DirectionForward, granularity);
532 if (killRing && selection.isCaret() && granularity != CharacterGranularity)
533 selection.modify(FrameSelection::AlterationExtend, DirectionForward, CharacterGranularity);
535 Position downstreamEnd = endingSelection().end().downstream();
536 VisiblePosition visibleEnd = endingSelection().visibleEnd();
537 Node* enclosingTableCell = enclosingNodeOfType(visibleEnd.deepEquivalent(), &isTableCell);
538 if (enclosingTableCell && visibleEnd == lastPositionInNode(enclosingTableCell))
540 if (visibleEnd == endOfParagraph(visibleEnd))
541 downstreamEnd = visibleEnd.next(CannotCrossEditingBoundary).deepEquivalent().downstream();
542 // When deleting tables: Select the table first, then perform the deletion
543 if (isRenderedTable(downstreamEnd.containerNode()) && downstreamEnd.computeOffsetInContainerNode() <= caretMinOffset(downstreamEnd.containerNode())) {
544 setEndingSelection(VisibleSelection(endingSelection().end(), positionAfterNode(downstreamEnd.containerNode()), DOWNSTREAM, endingSelection().isDirectional()));
545 typingAddedToOpenCommand(ForwardDeleteKey);
549 // deleting to end of paragraph when at end of paragraph needs to merge the next paragraph (if any)
550 if (granularity == ParagraphBoundary && selection.selection().isCaret() && isEndOfParagraph(selection.selection().visibleEnd()))
551 selection.modify(FrameSelection::AlterationExtend, DirectionForward, CharacterGranularity);
553 selectionToDelete = selection.selection();
554 if (!startingSelection().isRange() || selectionToDelete.base() != startingSelection().start())
555 selectionAfterUndo = selectionToDelete;
557 // It's a little tricky to compute what the starting selection would have been in the original document.
558 // We can't let the VisibleSelection class's validation kick in or it'll adjust for us based on
559 // the current state of the document and we'll get the wrong result.
560 Position extent = startingSelection().end();
561 if (extent.containerNode() != selectionToDelete.end().containerNode())
562 extent = selectionToDelete.extent();
565 if (selectionToDelete.start().containerNode() == selectionToDelete.end().containerNode())
566 extraCharacters = selectionToDelete.end().computeOffsetInContainerNode() - selectionToDelete.start().computeOffsetInContainerNode();
568 extraCharacters = selectionToDelete.end().computeOffsetInContainerNode();
569 extent = Position(extent.containerNode(), extent.computeOffsetInContainerNode() + extraCharacters, Position::PositionIsOffsetInAnchor);
571 selectionAfterUndo.setWithoutValidation(startingSelection().start(), extent);
576 ASSERT_NOT_REACHED();
580 ASSERT(!selectionToDelete.isNone());
581 if (selectionToDelete.isNone())
584 if (selectionToDelete.isCaret())
588 frame->editor().addToKillRing(selectionToDelete.toNormalizedRange().get(), false);
589 // Make undo select what was deleted on Mac alone
590 if (frame->editor().behavior().shouldUndoOfDeleteSelectText())
591 setStartingSelection(selectionAfterUndo);
592 CompositeEditCommand::deleteSelection(selectionToDelete, m_smartDelete);
593 setSmartDelete(false);
594 typingAddedToOpenCommand(ForwardDeleteKey);
597 void TypingCommand::deleteSelection(bool smartDelete)
599 CompositeEditCommand::deleteSelection(smartDelete);
600 typingAddedToOpenCommand(DeleteSelection);
603 void TypingCommand::updatePreservesTypingStyle(ETypingCommand commandType)
605 switch (commandType) {
606 case DeleteSelection:
608 case ForwardDeleteKey:
609 case InsertParagraphSeparator:
610 case InsertLineBreak:
611 m_preservesTypingStyle = true;
613 case InsertParagraphSeparatorInQuotedContent:
615 m_preservesTypingStyle = false;
618 ASSERT_NOT_REACHED();
619 m_preservesTypingStyle = false;
622 bool TypingCommand::isTypingCommand() const
627 } // namespace WebCore