This source file includes following definitions.
- m_text
- m_shouldPreventSpellChecking
- deleteSelection
- deleteKeyPressed
- forwardDeleteKeyPressed
- updateSelectionIfDifferentFromCurrentSelection
- insertText
- insertText
- insertLineBreak
- insertParagraphSeparatorInQuotedContent
- insertParagraphSeparator
- lastTypingCommandIfStillOpenForTyping
- closeTyping
- doApply
- editingAction
- markMisspellingsAfterTyping
- typingAddedToOpenCommand
- insertText
- insertTextRunWithoutNewlines
- insertLineBreak
- insertParagraphSeparator
- insertParagraphSeparatorInQuotedContent
- makeEditableRootEmpty
- deleteKeyPressed
- forwardDeleteKeyPressed
- deleteSelection
- updatePreservesTypingStyle
- isTypingCommand
#include "config.h"
#include "core/editing/TypingCommand.h"
#include "HTMLNames.h"
#include "core/dom/Document.h"
#include "core/dom/Element.h"
#include "core/dom/ElementTraversal.h"
#include "core/editing/BreakBlockquoteCommand.h"
#include "core/editing/Editor.h"
#include "core/editing/FrameSelection.h"
#include "core/editing/InsertLineBreakCommand.h"
#include "core/editing/InsertParagraphSeparatorCommand.h"
#include "core/editing/InsertTextCommand.h"
#include "core/editing/SpellChecker.h"
#include "core/editing/VisiblePosition.h"
#include "core/editing/VisibleUnits.h"
#include "core/editing/htmlediting.h"
#include "core/frame/LocalFrame.h"
#include "core/html/HTMLBRElement.h"
#include "core/rendering/RenderObject.h"
namespace WebCore {
using namespace HTMLNames;
class TypingCommandLineOperation
{
public:
TypingCommandLineOperation(TypingCommand* typingCommand, bool selectInsertedText, const String& text)
: m_typingCommand(typingCommand)
, m_selectInsertedText(selectInsertedText)
, m_text(text)
{ }
void operator()(size_t lineOffset, size_t lineLength, bool isLastLine) const
{
if (isLastLine) {
if (!lineOffset || lineLength > 0)
m_typingCommand->insertTextRunWithoutNewlines(m_text.substring(lineOffset, lineLength), m_selectInsertedText);
} else {
if (lineLength > 0)
m_typingCommand->insertTextRunWithoutNewlines(m_text.substring(lineOffset, lineLength), false);
m_typingCommand->insertParagraphSeparator();
}
}
private:
TypingCommand* m_typingCommand;
bool m_selectInsertedText;
const String& m_text;
};
TypingCommand::TypingCommand(Document& document, ETypingCommand commandType, const String &textToInsert, Options options, TextGranularity granularity, TextCompositionType compositionType)
: TextInsertionBaseCommand(document)
, m_commandType(commandType)
, m_textToInsert(textToInsert)
, m_openForMoreTyping(true)
, m_selectInsertedText(options & SelectInsertedText)
, m_smartDelete(options & SmartDelete)
, m_granularity(granularity)
, m_compositionType(compositionType)
, m_killRing(options & KillRing)
, m_openedByBackwardDelete(false)
, m_shouldRetainAutocorrectionIndicator(options & RetainAutocorrectionIndicator)
, m_shouldPreventSpellChecking(options & PreventSpellChecking)
{
updatePreservesTypingStyle(m_commandType);
}
void TypingCommand::deleteSelection(Document& document, Options options)
{
LocalFrame* frame = document.frame();
ASSERT(frame);
if (!frame->selection().isRange())
return;
if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(frame)) {
lastTypingCommand->setShouldPreventSpellChecking(options & PreventSpellChecking);
lastTypingCommand->deleteSelection(options & SmartDelete);
return;
}
TypingCommand::create(document, DeleteSelection, "", options)->apply();
}
void TypingCommand::deleteKeyPressed(Document& document, Options options, TextGranularity granularity)
{
if (granularity == CharacterGranularity) {
LocalFrame* frame = document.frame();
if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(frame)) {
if (lastTypingCommand->commandTypeOfOpenCommand() == DeleteKey) {
updateSelectionIfDifferentFromCurrentSelection(lastTypingCommand.get(), frame);
lastTypingCommand->setShouldPreventSpellChecking(options & PreventSpellChecking);
lastTypingCommand->deleteKeyPressed(granularity, options & KillRing);
return;
}
}
}
TypingCommand::create(document, DeleteKey, "", options, granularity)->apply();
}
void TypingCommand::forwardDeleteKeyPressed(Document& document, Options options, TextGranularity granularity)
{
if (granularity == CharacterGranularity) {
LocalFrame* frame = document.frame();
if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(frame)) {
updateSelectionIfDifferentFromCurrentSelection(lastTypingCommand.get(), frame);
lastTypingCommand->setShouldPreventSpellChecking(options & PreventSpellChecking);
lastTypingCommand->forwardDeleteKeyPressed(granularity, options & KillRing);
return;
}
}
TypingCommand::create(document, ForwardDeleteKey, "", options, granularity)->apply();
}
void TypingCommand::updateSelectionIfDifferentFromCurrentSelection(TypingCommand* typingCommand, LocalFrame* frame)
{
ASSERT(frame);
VisibleSelection currentSelection = frame->selection().selection();
if (currentSelection == typingCommand->endingSelection())
return;
typingCommand->setStartingSelection(currentSelection);
typingCommand->setEndingSelection(currentSelection);
}
void TypingCommand::insertText(Document& document, const String& text, Options options, TextCompositionType composition)
{
LocalFrame* frame = document.frame();
ASSERT(frame);
if (!text.isEmpty())
document.frame()->spellChecker().updateMarkersForWordsAffectedByEditing(isSpaceOrNewline(text[0]));
insertText(document, text, frame->selection().selection(), options, composition);
}
void TypingCommand::insertText(Document& document, const String& text, const VisibleSelection& selectionForInsertion, Options options, TextCompositionType compositionType)
{
RefPtr<LocalFrame> frame = document.frame();
ASSERT(frame);
VisibleSelection currentSelection = frame->selection().selection();
String newText = dispatchBeforeTextInsertedEvent(text, selectionForInsertion, compositionType == TextCompositionUpdate);
if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(frame.get())) {
if (lastTypingCommand->endingSelection() != selectionForInsertion) {
lastTypingCommand->setStartingSelection(selectionForInsertion);
lastTypingCommand->setEndingSelection(selectionForInsertion);
}
lastTypingCommand->setCompositionType(compositionType);
lastTypingCommand->setShouldRetainAutocorrectionIndicator(options & RetainAutocorrectionIndicator);
lastTypingCommand->setShouldPreventSpellChecking(options & PreventSpellChecking);
lastTypingCommand->insertText(newText, options & SelectInsertedText);
return;
}
RefPtr<TypingCommand> cmd = TypingCommand::create(document, InsertText, newText, options, compositionType);
applyTextInsertionCommand(frame.get(), cmd, selectionForInsertion, currentSelection);
}
void TypingCommand::insertLineBreak(Document& document, Options options)
{
if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(document.frame())) {
lastTypingCommand->setShouldRetainAutocorrectionIndicator(options & RetainAutocorrectionIndicator);
lastTypingCommand->insertLineBreak();
return;
}
TypingCommand::create(document, InsertLineBreak, "", options)->apply();
}
void TypingCommand::insertParagraphSeparatorInQuotedContent(Document& document)
{
if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(document.frame())) {
lastTypingCommand->insertParagraphSeparatorInQuotedContent();
return;
}
TypingCommand::create(document, InsertParagraphSeparatorInQuotedContent)->apply();
}
void TypingCommand::insertParagraphSeparator(Document& document, Options options)
{
if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(document.frame())) {
lastTypingCommand->setShouldRetainAutocorrectionIndicator(options & RetainAutocorrectionIndicator);
lastTypingCommand->insertParagraphSeparator();
return;
}
TypingCommand::create(document, InsertParagraphSeparator, "", options)->apply();
}
PassRefPtr<TypingCommand> TypingCommand::lastTypingCommandIfStillOpenForTyping(LocalFrame* frame)
{
ASSERT(frame);
RefPtr<CompositeEditCommand> lastEditCommand = frame->editor().lastEditCommand();
if (!lastEditCommand || !lastEditCommand->isTypingCommand() || !static_cast<TypingCommand*>(lastEditCommand.get())->isOpenForMoreTyping())
return nullptr;
return static_cast<TypingCommand*>(lastEditCommand.get());
}
void TypingCommand::closeTyping(LocalFrame* frame)
{
if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(frame))
lastTypingCommand->closeTyping();
}
void TypingCommand::doApply()
{
if (!endingSelection().isNonOrphanedCaretOrRange())
return;
if (m_commandType == DeleteKey)
if (m_commands.isEmpty())
m_openedByBackwardDelete = true;
switch (m_commandType) {
case DeleteSelection:
deleteSelection(m_smartDelete);
return;
case DeleteKey:
deleteKeyPressed(m_granularity, m_killRing);
return;
case ForwardDeleteKey:
forwardDeleteKeyPressed(m_granularity, m_killRing);
return;
case InsertLineBreak:
insertLineBreak();
return;
case InsertParagraphSeparator:
insertParagraphSeparator();
return;
case InsertParagraphSeparatorInQuotedContent:
insertParagraphSeparatorInQuotedContent();
return;
case InsertText:
insertText(m_textToInsert, m_selectInsertedText);
return;
}
ASSERT_NOT_REACHED();
}
EditAction TypingCommand::editingAction() const
{
return EditActionTyping;
}
void TypingCommand::markMisspellingsAfterTyping(ETypingCommand commandType)
{
LocalFrame* frame = document().frame();
if (!frame)
return;
if (!frame->spellChecker().isContinuousSpellCheckingEnabled())
return;
frame->spellChecker().cancelCheck();
VisiblePosition start(endingSelection().start(), endingSelection().affinity());
VisiblePosition previous = start.previous();
if (previous.isNotNull()) {
VisiblePosition p1 = startOfWord(previous, LeftWordIfOnBoundary);
VisiblePosition p2 = startOfWord(start, LeftWordIfOnBoundary);
if (p1 != p2)
frame->spellChecker().markMisspellingsAfterTypingToWord(p1, endingSelection());
}
}
void TypingCommand::typingAddedToOpenCommand(ETypingCommand commandTypeForAddedTyping)
{
LocalFrame* frame = document().frame();
if (!frame)
return;
updatePreservesTypingStyle(commandTypeForAddedTyping);
updateCommandTypeOfOpenCommand(commandTypeForAddedTyping);
markMisspellingsAfterTyping(commandTypeForAddedTyping);
frame->editor().appliedEditing(this);
}
void TypingCommand::insertText(const String &text, bool selectInsertedText)
{
TypingCommandLineOperation operation(this, selectInsertedText, text);
forEachLineInString(text, operation);
}
void TypingCommand::insertTextRunWithoutNewlines(const String &text, bool selectInsertedText)
{
RefPtr<InsertTextCommand> command = InsertTextCommand::create(document(), text, selectInsertedText,
m_compositionType == TextCompositionNone ? InsertTextCommand::RebalanceLeadingAndTrailingWhitespaces : InsertTextCommand::RebalanceAllWhitespaces);
applyCommandToComposite(command, endingSelection());
typingAddedToOpenCommand(InsertText);
}
void TypingCommand::insertLineBreak()
{
if (!canAppendNewLineFeedToSelection(endingSelection()))
return;
applyCommandToComposite(InsertLineBreakCommand::create(document()));
typingAddedToOpenCommand(InsertLineBreak);
}
void TypingCommand::insertParagraphSeparator()
{
if (!canAppendNewLineFeedToSelection(endingSelection()))
return;
applyCommandToComposite(InsertParagraphSeparatorCommand::create(document()));
typingAddedToOpenCommand(InsertParagraphSeparator);
}
void TypingCommand::insertParagraphSeparatorInQuotedContent()
{
if (enclosingNodeOfType(endingSelection().start(), &isTableStructureNode)) {
insertParagraphSeparator();
return;
}
applyCommandToComposite(BreakBlockquoteCommand::create(document()));
typingAddedToOpenCommand(InsertParagraphSeparatorInQuotedContent);
}
bool TypingCommand::makeEditableRootEmpty()
{
Element* root = endingSelection().rootEditableElement();
if (!root || !root->firstChild())
return false;
if (root->firstChild() == root->lastChild()) {
Element* firstElementChild = ElementTraversal::firstWithin(*root);
if (isHTMLBRElement(firstElementChild)) {
if (root->renderer() && root->renderer()->isRenderBlockFlow())
return false;
}
}
while (Node* child = root->firstChild())
removeNode(child);
addBlockPlaceholderIfNeeded(root);
setEndingSelection(VisibleSelection(firstPositionInNode(root), DOWNSTREAM, endingSelection().isDirectional()));
return true;
}
void TypingCommand::deleteKeyPressed(TextGranularity granularity, bool killRing)
{
LocalFrame* frame = document().frame();
if (!frame)
return;
frame->spellChecker().updateMarkersForWordsAffectedByEditing(false);
VisibleSelection selectionToDelete;
VisibleSelection selectionAfterUndo;
switch (endingSelection().selectionType()) {
case RangeSelection:
selectionToDelete = endingSelection();
selectionAfterUndo = selectionToDelete;
break;
case CaretSelection: {
if (breakOutOfEmptyMailBlockquotedParagraph())
typingAddedToOpenCommand(DeleteKey);
m_smartDelete = false;
FrameSelection selection;
selection.setSelection(endingSelection());
selection.modify(FrameSelection::AlterationExtend, DirectionBackward, granularity);
if (killRing && selection.isCaret() && granularity != CharacterGranularity)
selection.modify(FrameSelection::AlterationExtend, DirectionBackward, CharacterGranularity);
VisiblePosition visibleStart(endingSelection().visibleStart());
if (visibleStart.previous(CannotCrossEditingBoundary).isNull()) {
if (breakOutOfEmptyListItem()) {
typingAddedToOpenCommand(DeleteKey);
return;
}
if (visibleStart.next(CannotCrossEditingBoundary).isNull() && makeEditableRootEmpty()) {
typingAddedToOpenCommand(DeleteKey);
return;
}
}
Node* enclosingTableCell = enclosingNodeOfType(visibleStart.deepEquivalent(), &isTableCell);
if (enclosingTableCell && visibleStart == VisiblePosition(firstPositionInNode(enclosingTableCell)))
return;
if (isStartOfParagraph(visibleStart) && isFirstPositionAfterTable(visibleStart.previous(CannotCrossEditingBoundary))) {
if (isLastPositionBeforeTable(visibleStart))
return;
selection.modify(FrameSelection::AlterationExtend, DirectionBackward, granularity);
} else if (Node* table = isFirstPositionAfterTable(visibleStart)) {
setEndingSelection(VisibleSelection(positionBeforeNode(table), endingSelection().start(), DOWNSTREAM, endingSelection().isDirectional()));
typingAddedToOpenCommand(DeleteKey);
return;
}
selectionToDelete = selection.selection();
if (granularity == CharacterGranularity && selectionToDelete.end().containerNode() == selectionToDelete.start().containerNode()
&& selectionToDelete.end().computeOffsetInContainerNode() - selectionToDelete.start().computeOffsetInContainerNode() > 1) {
selectionToDelete.setWithoutValidation(selectionToDelete.end(), selectionToDelete.end().previous(BackwardDeletion));
}
if (!startingSelection().isRange() || selectionToDelete.base() != startingSelection().start())
selectionAfterUndo = selectionToDelete;
else
selectionAfterUndo.setWithoutValidation(startingSelection().end(), selectionToDelete.extent());
break;
}
case NoSelection:
ASSERT_NOT_REACHED();
break;
}
ASSERT(!selectionToDelete.isNone());
if (selectionToDelete.isNone())
return;
if (selectionToDelete.isCaret())
return;
if (killRing)
frame->editor().addToKillRing(selectionToDelete.toNormalizedRange().get(), false);
if (frame->editor().behavior().shouldUndoOfDeleteSelectText() && m_openedByBackwardDelete)
setStartingSelection(selectionAfterUndo);
CompositeEditCommand::deleteSelection(selectionToDelete, m_smartDelete);
setSmartDelete(false);
typingAddedToOpenCommand(DeleteKey);
}
void TypingCommand::forwardDeleteKeyPressed(TextGranularity granularity, bool killRing)
{
LocalFrame* frame = document().frame();
if (!frame)
return;
frame->spellChecker().updateMarkersForWordsAffectedByEditing(false);
VisibleSelection selectionToDelete;
VisibleSelection selectionAfterUndo;
switch (endingSelection().selectionType()) {
case RangeSelection:
selectionToDelete = endingSelection();
selectionAfterUndo = selectionToDelete;
break;
case CaretSelection: {
m_smartDelete = false;
FrameSelection selection;
selection.setSelection(endingSelection());
selection.modify(FrameSelection::AlterationExtend, DirectionForward, granularity);
if (killRing && selection.isCaret() && granularity != CharacterGranularity)
selection.modify(FrameSelection::AlterationExtend, DirectionForward, CharacterGranularity);
Position downstreamEnd = endingSelection().end().downstream();
VisiblePosition visibleEnd = endingSelection().visibleEnd();
Node* enclosingTableCell = enclosingNodeOfType(visibleEnd.deepEquivalent(), &isTableCell);
if (enclosingTableCell && visibleEnd == VisiblePosition(lastPositionInNode(enclosingTableCell)))
return;
if (visibleEnd == endOfParagraph(visibleEnd))
downstreamEnd = visibleEnd.next(CannotCrossEditingBoundary).deepEquivalent().downstream();
if (isRenderedTable(downstreamEnd.containerNode()) && downstreamEnd.computeOffsetInContainerNode() <= caretMinOffset(downstreamEnd.containerNode())) {
setEndingSelection(VisibleSelection(endingSelection().end(), positionAfterNode(downstreamEnd.containerNode()), DOWNSTREAM, endingSelection().isDirectional()));
typingAddedToOpenCommand(ForwardDeleteKey);
return;
}
if (granularity == ParagraphBoundary && selection.selection().isCaret() && isEndOfParagraph(selection.selection().visibleEnd()))
selection.modify(FrameSelection::AlterationExtend, DirectionForward, CharacterGranularity);
selectionToDelete = selection.selection();
if (!startingSelection().isRange() || selectionToDelete.base() != startingSelection().start())
selectionAfterUndo = selectionToDelete;
else {
Position extent = startingSelection().end();
if (extent.containerNode() != selectionToDelete.end().containerNode())
extent = selectionToDelete.extent();
else {
int extraCharacters;
if (selectionToDelete.start().containerNode() == selectionToDelete.end().containerNode())
extraCharacters = selectionToDelete.end().computeOffsetInContainerNode() - selectionToDelete.start().computeOffsetInContainerNode();
else
extraCharacters = selectionToDelete.end().computeOffsetInContainerNode();
extent = Position(extent.containerNode(), extent.computeOffsetInContainerNode() + extraCharacters, Position::PositionIsOffsetInAnchor);
}
selectionAfterUndo.setWithoutValidation(startingSelection().start(), extent);
}
break;
}
case NoSelection:
ASSERT_NOT_REACHED();
break;
}
ASSERT(!selectionToDelete.isNone());
if (selectionToDelete.isNone())
return;
if (selectionToDelete.isCaret())
return;
if (killRing)
frame->editor().addToKillRing(selectionToDelete.toNormalizedRange().get(), false);
if (frame->editor().behavior().shouldUndoOfDeleteSelectText())
setStartingSelection(selectionAfterUndo);
CompositeEditCommand::deleteSelection(selectionToDelete, m_smartDelete);
setSmartDelete(false);
typingAddedToOpenCommand(ForwardDeleteKey);
}
void TypingCommand::deleteSelection(bool smartDelete)
{
CompositeEditCommand::deleteSelection(smartDelete);
typingAddedToOpenCommand(DeleteSelection);
}
void TypingCommand::updatePreservesTypingStyle(ETypingCommand commandType)
{
switch (commandType) {
case DeleteSelection:
case DeleteKey:
case ForwardDeleteKey:
case InsertParagraphSeparator:
case InsertLineBreak:
m_preservesTypingStyle = true;
return;
case InsertParagraphSeparatorInQuotedContent:
case InsertText:
m_preservesTypingStyle = false;
return;
}
ASSERT_NOT_REACHED();
m_preservesTypingStyle = false;
}
bool TypingCommand::isTypingCommand() const
{
return true;
}
}