Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(85)

Unified Diff: third_party/WebKit/Source/core/editing/commands/TypingCommand.cpp

Issue 2530843003: Introduce InsertIncrementalTextCommand to respect existing style for composition (Closed)
Patch Set: Created 4 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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);
}

Powered by Google App Engine
This is Rietveld 408576698