Chromium Code Reviews| 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, |
| } |
| } |
|
yabinh
2016/11/25 04:36:27
The followings functions (till Line 335) are moved
chongz
2016/11/28 16:01:04
I recalled that yosin@ doesn't want extra logic in
yabinh
2016/11/29 02:15:21
I think what yosin@ doesn't want is an extra optio
yosin_UTC9
2016/11/29 03:31:33
chognze@ is right. Our conclusion is introducing T
yabinh
2016/12/02 08:41:55
Done. Introduced a new command in patch set 4.
|
| +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; |
| +} |
| + |
|
yabinh
2016/11/25 04:36:27
The above functions are moved from InputMethodCont
|
| +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); |
| } |