| Index: third_party/WebKit/Source/core/editing/commands/TypingCommand.cpp
|
| diff --git a/third_party/WebKit/Source/core/editing/commands/TypingCommand.cpp b/third_party/WebKit/Source/core/editing/commands/TypingCommand.cpp
|
| index 68aee201cbbe8b3e2226b5d4b3e667d7bc59ff60..d9ca306dff74e8357d101c39dcdbb4bec8056c32 100644
|
| --- a/third_party/WebKit/Source/core/editing/commands/TypingCommand.cpp
|
| +++ b/third_party/WebKit/Source/core/editing/commands/TypingCommand.cpp
|
| @@ -31,6 +31,7 @@
|
| #include "core/dom/ElementTraversal.h"
|
| #include "core/editing/EditingUtilities.h"
|
| #include "core/editing/Editor.h"
|
| +#include "core/editing/PlainTextRange.h"
|
| #include "core/editing/SelectionModifier.h"
|
| #include "core/editing/VisiblePosition.h"
|
| #include "core/editing/VisibleUnits.h"
|
| @@ -40,6 +41,7 @@
|
| #include "core/editing/commands/InsertTextCommand.h"
|
| #include "core/editing/spellcheck/SpellChecker.h"
|
| #include "core/events/BeforeTextInsertedEvent.h"
|
| +#include "core/events/TextEvent.h"
|
| #include "core/frame/LocalFrame.h"
|
| #include "core/html/HTMLBRElement.h"
|
| #include "core/layout/LayoutObject.h"
|
| @@ -47,6 +49,7 @@
|
| namespace blink {
|
|
|
| using namespace HTMLNames;
|
| +bool TypingCommand::m_isIncrementalInsertion = false;
|
|
|
| TypingCommand::TypingCommand(Document& document,
|
| ETypingCommand commandType,
|
| @@ -57,6 +60,7 @@ TypingCommand::TypingCommand(Document& document,
|
| : CompositeEditCommand(document),
|
| m_commandType(commandType),
|
| m_textToInsert(textToInsert),
|
| + m_textForEvent(textToInsert),
|
| m_openForMoreTyping(true),
|
| m_selectInsertedText(options & SelectInsertedText),
|
| m_smartDelete(options & SmartDelete),
|
| @@ -143,6 +147,8 @@ void TypingCommand::forwardDeleteKeyPressed(Document& document,
|
| }
|
|
|
| String TypingCommand::textDataForInputEvent() const {
|
| + if (m_isIncrementalInsertion)
|
| + return m_textForEvent;
|
| if (m_commands.isEmpty())
|
| return m_textToInsert;
|
| return m_commands.back()->textDataForInputEvent();
|
| @@ -162,11 +168,7 @@ void TypingCommand::updateSelectionIfDifferentFromCurrentSelection(
|
|
|
| static String dispatchBeforeTextInsertedEvent(
|
| const String& text,
|
| - const VisibleSelection& selectionForInsertion,
|
| - bool insertionIsForUpdatingComposition) {
|
| - if (insertionIsForUpdatingComposition)
|
| - return text;
|
| -
|
| + const VisibleSelection& selectionForInsertion) {
|
| String newText = text;
|
| if (Node* startNode = selectionForInsertion.start().computeContainerNode()) {
|
| if (rootEditableElement(*startNode)) {
|
| @@ -207,8 +209,9 @@ void TypingCommand::insertText(Document& document,
|
|
|
| VisibleSelection currentSelection = frame->selection().selection();
|
|
|
| - String newText = dispatchBeforeTextInsertedEvent(
|
| - text, selectionForInsertion, compositionType == TextCompositionUpdate);
|
| + String newText = text;
|
| + if (compositionType != TextCompositionUpdate)
|
| + newText = dispatchBeforeTextInsertedEvent(text, selectionForInsertion);
|
|
|
| // Set the starting and ending selection appropriately if we are using a
|
| // selection that is different from the current selection. In the future, we
|
| @@ -227,8 +230,8 @@ void TypingCommand::insertText(Document& document,
|
| lastTypingCommand->setShouldPreventSpellChecking(options &
|
| PreventSpellChecking);
|
| EditingState editingState;
|
| - lastTypingCommand->insertText(newText, options & SelectInsertedText,
|
| - &editingState);
|
| + lastTypingCommand->insertText(newText, newText,
|
| + options & SelectInsertedText, &editingState);
|
| // Nothing to do even if the command was aborted.
|
| return;
|
| }
|
| @@ -247,6 +250,284 @@ void TypingCommand::insertText(Document& document,
|
| }
|
| }
|
|
|
| +static size_t computeCommonPrefixLength(const String& str1,
|
| + const String& str2) {
|
| + const size_t maxCommonPrefixLength = std::min(str1.length(), str2.length());
|
| + for (size_t index = 0; index < maxCommonPrefixLength; ++index) {
|
| + if (str1[index] != str2[index])
|
| + return index;
|
| + }
|
| + return maxCommonPrefixLength;
|
| +}
|
| +
|
| +static size_t computeCommonSuffixLength(const String& str1,
|
| + const String& str2) {
|
| + const size_t length1 = str1.length();
|
| + const size_t length2 = str2.length();
|
| + const size_t maxCommonSuffixLength = std::min(length1, length2);
|
| + for (size_t index = 0; index < maxCommonSuffixLength; ++index) {
|
| + if (str1[length1 - index - 1] != str2[length2 - index - 1])
|
| + return index;
|
| + }
|
| + return maxCommonSuffixLength;
|
| +}
|
| +
|
| +// If current position is at grapheme boundary, return 0; otherwise, return the
|
| +// distance to its nearest left grapheme boundary.
|
| +static size_t computeDistanceToLeftGraphemeBoundary(const Position& position) {
|
| + const Position& adjustedPosition = previousPositionOf(
|
| + nextPositionOf(position, PositionMoveType::GraphemeCluster),
|
| + PositionMoveType::GraphemeCluster);
|
| + DCHECK_EQ(position.anchorNode(), adjustedPosition.anchorNode());
|
| + DCHECK_GE(position.computeOffsetInContainerNode(),
|
| + adjustedPosition.computeOffsetInContainerNode());
|
| + return static_cast<size_t>(position.computeOffsetInContainerNode() -
|
| + adjustedPosition.computeOffsetInContainerNode());
|
| +}
|
| +
|
| +static size_t computeCommonGraphemeClusterPrefixLength(
|
| + const String& oldText,
|
| + const String& newText,
|
| + const Element* rootEditableElement) {
|
| + const size_t commonPrefixLength = computeCommonPrefixLength(oldText, newText);
|
| +
|
| + // For grapheme cluster, we should adjust it for grapheme boundary.
|
| + const EphemeralRange& range =
|
| + PlainTextRange(0, commonPrefixLength).createRange(*rootEditableElement);
|
| + if (range.isNull())
|
| + return 0;
|
| + const Position& position = range.endPosition();
|
| + const size_t diff = computeDistanceToLeftGraphemeBoundary(position);
|
| + DCHECK_GE(commonPrefixLength, diff);
|
| + return commonPrefixLength - diff;
|
| +}
|
| +
|
| +// If current position is at grapheme boundary, return 0; otherwise, return the
|
| +// distance to its nearest right grapheme boundary.
|
| +static size_t computeDistanceToRightGraphemeBoundary(const Position& position) {
|
| + const Position& adjustedPosition = nextPositionOf(
|
| + previousPositionOf(position, PositionMoveType::GraphemeCluster),
|
| + PositionMoveType::GraphemeCluster);
|
| + DCHECK_EQ(position.anchorNode(), adjustedPosition.anchorNode());
|
| + DCHECK_GE(adjustedPosition.computeOffsetInContainerNode(),
|
| + position.computeOffsetInContainerNode());
|
| + return static_cast<size_t>(adjustedPosition.computeOffsetInContainerNode() -
|
| + position.computeOffsetInContainerNode());
|
| +}
|
| +
|
| +static size_t computeCommonGraphemeClusterSuffixLength(
|
| + const String& oldText,
|
| + const String& newText,
|
| + const Element* rootEditableElement) {
|
| + const size_t commonSuffixLength = computeCommonSuffixLength(oldText, newText);
|
| +
|
| + // For grapheme cluster, we should adjust it for grapheme boundary.
|
| + const EphemeralRange& range =
|
| + PlainTextRange(0, oldText.length() - commonSuffixLength)
|
| + .createRange(*rootEditableElement);
|
| + if (range.isNull())
|
| + return 0;
|
| + const Position& position = range.endPosition();
|
| + const size_t diff = computeDistanceToRightGraphemeBoundary(position);
|
| + DCHECK_GE(commonSuffixLength, diff);
|
| + return commonSuffixLength - diff;
|
| +}
|
| +
|
| +static PlainTextRange getSelectionOffsets(LocalFrame* frame) {
|
| + EphemeralRange range = firstEphemeralRangeOf(frame->selection().selection());
|
| + if (range.isNull())
|
| + return PlainTextRange();
|
| + ContainerNode* editable =
|
| + frame->selection().rootEditableElementOrTreeScopeRootNode();
|
| + DCHECK(editable);
|
| + return PlainTextRange::create(*editable, range);
|
| +}
|
| +
|
| +static const VisibleSelection createSelectionForIncrementalInsertion(
|
| + const size_t start,
|
| + const size_t end,
|
| + const bool isDirectional,
|
| + LocalFrame* frame) {
|
| + Element* element = frame->selection().selection().rootEditableElement();
|
| + DCHECK(element);
|
| +
|
| + const EphemeralRange& startRange =
|
| + PlainTextRange(0, static_cast<int>(start)).createRange(*element);
|
| + DCHECK(startRange.isNotNull());
|
| + const Position& startPosition = startRange.endPosition();
|
| +
|
| + const EphemeralRange& endRange =
|
| + PlainTextRange(0, static_cast<int>(end)).createRange(*element);
|
| + DCHECK(endRange.isNotNull());
|
| + const Position& endPosition = endRange.endPosition();
|
| +
|
| + VisibleSelection selection =
|
| + createVisibleSelection(SelectionInDOMTree::Builder()
|
| + .setBaseAndExtent(startPosition, endPosition)
|
| + .build());
|
| + selection.setIsDirectional(isDirectional);
|
| +
|
| + return selection;
|
| +}
|
| +
|
| +void TypingCommand::setSelectionForIncrementalInsertion(
|
| + TypingCommand* command,
|
| + const VisibleSelection& newSelection,
|
| + LocalFrame* frame) {
|
| + command->setStartingSelection(newSelection);
|
| + command->setEndingVisibleSelection(newSelection);
|
| + frame->selection().setSelection(newSelection);
|
| +}
|
| +
|
| +void TypingCommand::setCommandSelectionForIncrementalInsertion(
|
| + TypingCommand* command,
|
| + const VisibleSelection& newSelection,
|
| + LocalFrame* frame) {
|
| + command->setStartingSelection(newSelection);
|
| + command->setEndingVisibleSelection(newSelection);
|
| +}
|
| +
|
| +void TypingCommand::insertIncrementalText(Document& document,
|
| + const String& oldText,
|
| + const String& newText,
|
| + Options options,
|
| + TextCompositionType composition) {
|
| + LocalFrame* frame = document.frame();
|
| + DCHECK(frame);
|
| +
|
| + if (!newText.isEmpty()) {
|
| + document.frame()->spellChecker().updateMarkersForWordsAffectedByEditing(
|
| + isSpaceOrNewline(newText[0]));
|
| + }
|
| +
|
| + m_isIncrementalInsertion = true;
|
| + insertIncrementalText(document, oldText, newText,
|
| + frame->selection().selection(), options, composition);
|
| +}
|
| +
|
| +// FIXME: We shouldn't need to take selectionForInsertion. It should be
|
| +// identical to FrameSelection's current selection.
|
| +void TypingCommand::insertIncrementalText(Document& document,
|
| + const String& oldText,
|
| + const String& newText,
|
| + const VisibleSelection& composition,
|
| + Options options,
|
| + TextCompositionType compositionType) {
|
| + LocalFrame* frame = document.frame();
|
| + DCHECK(frame);
|
| +
|
| + const VisibleSelection currentSelection = frame->selection().selection();
|
| +
|
| + String textForEvent = newText;
|
| + if (compositionType != TextCompositionUpdate) // == confirm???no use???
|
| + textForEvent = dispatchBeforeTextInsertedEvent(newText, composition);
|
| +
|
| + const Element* element = currentSelection.rootEditableElement();
|
| + DCHECK(element);
|
| +
|
| + const size_t eventTextLength = textForEvent.length();
|
| + const size_t commonPrefixLength =
|
| + computeCommonGraphemeClusterPrefixLength(oldText, textForEvent, element);
|
| + // We should ignore common prefix when finding common suffix.
|
| + const size_t commonSuffixLength = computeCommonGraphemeClusterSuffixLength(
|
| + oldText.right(oldText.length() - commonPrefixLength),
|
| + textForEvent.right(eventTextLength - commonPrefixLength), element);
|
| +
|
| + const String& textToInsert = textForEvent.substring(
|
| + commonPrefixLength,
|
| + eventTextLength - commonPrefixLength - commonSuffixLength);
|
| +
|
| + PlainTextRange selectionOffsets = getSelectionOffsets(frame);
|
| + const size_t selecitonStart = selectionOffsets.start();
|
| + const size_t selectionEnd = selectionOffsets.end();
|
| +
|
| + const size_t insertionStart = selecitonStart + commonPrefixLength;
|
| + const size_t insertionEnd = selectionEnd - commonSuffixLength;
|
| + DCHECK_LE(insertionStart, insertionEnd);
|
| + const VisibleSelection selectionForInsertion =
|
| + createSelectionForIncrementalInsertion(insertionStart, insertionEnd,
|
| + currentSelection.isDirectional(),
|
| + frame);
|
| +
|
| + // Set the starting and ending selection appropriately if we are using a
|
| + // selection that is different from the current selection. In the future, we
|
| + // should change EditCommand to deal with custom selections in a general way
|
| + // that can be used by all of the commands.
|
| + if (TypingCommand* lastTypingCommand =
|
| + lastTypingCommandIfStillOpenForTyping(frame)) {
|
| + if (lastTypingCommand->endingSelection() != selectionForInsertion) {
|
| + setCommandSelectionForIncrementalInsertion(lastTypingCommand,
|
| + selectionForInsertion, frame);
|
| + }
|
| +
|
| + lastTypingCommand->setCompositionType(compositionType);
|
| + lastTypingCommand->setShouldRetainAutocorrectionIndicator(
|
| + options & RetainAutocorrectionIndicator);
|
| + lastTypingCommand->setShouldPreventSpellChecking(options &
|
| + PreventSpellChecking);
|
| +
|
| + EditingState editingState;
|
| +
|
| + const bool changeSelection = selectionForInsertion != currentSelection;
|
| +
|
| + // Select the text to be deleted.
|
| + if (changeSelection) {
|
| + setCommandSelectionForIncrementalInsertion(lastTypingCommand,
|
| + selectionForInsertion, frame);
|
| + }
|
| +
|
| + // Insert the incremental text |textToInsert| and send "input" event with
|
| + // the whole text |textForEvent|.
|
| + lastTypingCommand->insertText(textToInsert, textForEvent,
|
| + options & SelectInsertedText, &editingState);
|
| +
|
| + // Change the selection back.
|
| + if (changeSelection) {
|
| + // TODO(xiaochengh): The use of
|
| + // updateStyleAndLayoutIgnorePendingStylesheets
|
| + // needs to be audited. see http://crbug.com/590369 for more details.
|
| + document.updateStyleAndLayoutIgnorePendingStylesheets();
|
| +
|
| + const VisibleSelection updatedSelection =
|
| + createSelectionForIncrementalInsertion(
|
| + selecitonStart, selecitonStart + eventTextLength,
|
| + currentSelection.isDirectional(), frame);
|
| + setSelectionForIncrementalInsertion(lastTypingCommand, updatedSelection,
|
| + frame);
|
| + }
|
| + // Nothing to do even if the command was aborted.
|
| + return;
|
| + }
|
| +
|
| + TypingCommand* command = TypingCommand::create(
|
| + document, InsertText, textToInsert, options, compositionType);
|
| +
|
| + const bool changeSelection = selectionForInsertion != currentSelection;
|
| +
|
| + // Select the text to be deleted.
|
| + if (changeSelection) {
|
| + setCommandSelectionForIncrementalInsertion(command, selectionForInsertion,
|
| + frame);
|
| + }
|
| +
|
| + // Insert the incremental text |textToInsert| and send "input" event with
|
| + // the whole text |textForEvent|.
|
| + command->apply();
|
| +
|
| + // Change the selection back.
|
| + if (changeSelection) {
|
| + // TODO(xiaochengh): The use of updateStyleAndLayoutIgnorePendingStylesheets
|
| + // needs to be audited. see http://crbug.com/590369 for more details.
|
| + document.updateStyleAndLayoutIgnorePendingStylesheets();
|
| +
|
| + const VisibleSelection updatedSelection =
|
| + createSelectionForIncrementalInsertion(
|
| + selecitonStart, selecitonStart + eventTextLength,
|
| + currentSelection.isDirectional(), frame);
|
| + setSelectionForIncrementalInsertion(command, updatedSelection, frame);
|
| + }
|
| +}
|
| +
|
| bool TypingCommand::insertLineBreak(Document& document) {
|
| if (TypingCommand* lastTypingCommand =
|
| lastTypingCommandIfStillOpenForTyping(document.frame())) {
|
| @@ -333,7 +614,8 @@ void TypingCommand::doApply(EditingState* editingState) {
|
| insertParagraphSeparatorInQuotedContent(editingState);
|
| return;
|
| case InsertText:
|
| - insertText(m_textToInsert, m_selectInsertedText, editingState);
|
| + insertText(m_textToInsert, m_textForEvent, m_selectInsertedText,
|
| + editingState);
|
| return;
|
| }
|
|
|
| @@ -382,11 +664,13 @@ void TypingCommand::typingAddedToOpenCommand(
|
| frame->editor().appliedEditing(this);
|
| }
|
|
|
| -void TypingCommand::insertText(const String& text,
|
| +void TypingCommand::insertText(const String& textToInsert,
|
| + const String& textForEvent,
|
| bool selectInsertedText,
|
| EditingState* editingState) {
|
| - if (text.isEmpty()) {
|
| - insertTextRunWithoutNewlines(text, selectInsertedText, editingState);
|
| + if (textToInsert.isEmpty()) {
|
| + insertTextRunWithoutNewlines(textToInsert, textForEvent, selectInsertedText,
|
| + editingState);
|
| return;
|
| }
|
| // FIXME: Need to implement selectInsertedText for cases where more than one
|
| @@ -398,11 +682,12 @@ void TypingCommand::insertText(const String& text,
|
| // inserted text.
|
| unsigned offset = 0;
|
| size_t newline;
|
| - while ((newline = text.find('\n', offset)) != kNotFound) {
|
| + while ((newline = textToInsert.find('\n', offset)) != kNotFound) {
|
| if (newline > offset) {
|
| const bool notSelectInsertedText = false;
|
| - insertTextRunWithoutNewlines(text.substring(offset, newline - offset),
|
| - notSelectInsertedText, editingState);
|
| + insertTextRunWithoutNewlines(
|
| + textToInsert.substring(offset, newline - offset), textForEvent,
|
| + notSelectInsertedText, editingState);
|
| if (editingState->isAborted())
|
| return;
|
| }
|
| @@ -415,20 +700,24 @@ void TypingCommand::insertText(const String& text,
|
| }
|
|
|
| if (!offset) {
|
| - insertTextRunWithoutNewlines(text, selectInsertedText, editingState);
|
| + insertTextRunWithoutNewlines(textToInsert, textForEvent, selectInsertedText,
|
| + editingState);
|
| return;
|
| }
|
|
|
| - if (text.length() > offset)
|
| - insertTextRunWithoutNewlines(text.substring(offset, text.length() - offset),
|
| - selectInsertedText, editingState);
|
| + if (textToInsert.length() > offset) {
|
| + insertTextRunWithoutNewlines(
|
| + textToInsert.substring(offset, textToInsert.length() - offset),
|
| + textForEvent, selectInsertedText, editingState);
|
| + }
|
| }
|
|
|
| -void TypingCommand::insertTextRunWithoutNewlines(const String& text,
|
| +void TypingCommand::insertTextRunWithoutNewlines(const String& textToInsert,
|
| + const String& textForEvent,
|
| bool selectInsertedText,
|
| EditingState* editingState) {
|
| InsertTextCommand* command = InsertTextCommand::create(
|
| - document(), text, selectInsertedText,
|
| + document(), textToInsert, selectInsertedText,
|
| m_compositionType == TextCompositionNone
|
| ? InsertTextCommand::RebalanceLeadingAndTrailingWhitespaces
|
| : InsertTextCommand::RebalanceAllWhitespaces);
|
| @@ -436,6 +725,8 @@ void TypingCommand::insertTextRunWithoutNewlines(const String& text,
|
| applyCommandToComposite(command, endingSelection(), editingState);
|
| if (editingState->isAborted())
|
| return;
|
| +
|
| + m_textForEvent = textForEvent;
|
| typingAddedToOpenCommand(InsertText);
|
| }
|
|
|
|
|