Index: third_party/WebKit/Source/core/editing/InputMethodController.cpp |
diff --git a/third_party/WebKit/Source/core/editing/InputMethodController.cpp b/third_party/WebKit/Source/core/editing/InputMethodController.cpp |
index 6b6569806313de4431994a4ede65ea2287346bee..96f5ef7257165ee434fc85950ce3490e9e4558d9 100644 |
--- a/third_party/WebKit/Source/core/editing/InputMethodController.cpp |
+++ b/third_party/WebKit/Source/core/editing/InputMethodController.cpp |
@@ -76,9 +76,10 @@ void dispatchCompositionEndEvent(LocalFrame& frame, const String& text) { |
// 2. Fire 'compositionupdate' event |
// 3. Fire TextEvent and modify DOM |
// TODO(chongz): 4. Fire 'input' event |
-void insertTextDuringCompositionWithEvents( |
+void insertIncrementtalTextDuringCompositionWithEvents( |
LocalFrame& frame, |
- const String& text, |
+ const String& oldText, |
+ const String& newText, |
TypingCommand::Options options, |
TypingCommand::TextCompositionType compositionType) { |
DCHECK(compositionType == |
@@ -107,7 +108,8 @@ void insertTextDuringCompositionWithEvents( |
? InputEvent::EventCancelable::NotCancelable |
: InputEvent::EventCancelable::IsCancelable; |
DispatchEventResult result = dispatchBeforeInputFromComposition( |
- target, InputEvent::InputType::InsertText, text, beforeInputCancelable); |
+ target, InputEvent::InputType::InsertText, newText, |
+ beforeInputCancelable); |
if (beforeInputCancelable == InputEvent::EventCancelable::IsCancelable && |
result != DispatchEventResult::NotCanceled) |
@@ -117,22 +119,30 @@ void insertTextDuringCompositionWithEvents( |
if (!frame.document()) |
return; |
- dispatchCompositionUpdateEvent(frame, text); |
+ dispatchCompositionUpdateEvent(frame, newText); |
// 'compositionupdate' event handler may destroy document. |
if (!frame.document()) |
return; |
switch (compositionType) { |
case TypingCommand::TextCompositionType::TextCompositionUpdate: |
- TypingCommand::insertText(*frame.document(), text, options, |
- compositionType); |
+ TypingCommand::insertIncrementalText(*frame.document(), oldText, newText, |
+ options, compositionType); |
break; |
case TypingCommand::TextCompositionType::TextCompositionConfirm: |
+ // When there is previous composition, and call commitText() |
+ // (or setComposition()) with empty text, |newText| will be empty. In that |
+ // case, we should do nothing to avoid firing additional events. |
+ if (newText.length()) { |
+ TypingCommand::insertIncrementalText(*frame.document(), oldText, |
+ newText, options, compositionType); |
+ } |
+ break; |
case TypingCommand::TextCompositionType::TextCompositionCancel: |
// TODO(chongz): Use TypingCommand::insertText after TextEvent was |
// removed. (Removed from spec since 2012) |
// See TextEvent.idl. |
- frame.eventHandler().handleTextInputEvent(text, 0, |
+ frame.eventHandler().handleTextInputEvent(newText, 0, |
TextEventInputComposition); |
break; |
default: |
@@ -171,7 +181,7 @@ InputMethodController* InputMethodController::create(LocalFrame& frame) { |
} |
InputMethodController::InputMethodController(LocalFrame& frame) |
- : m_frame(&frame), m_isDirty(false), m_hasComposition(false) {} |
+ : m_frame(&frame), m_hasComposition(false) {} |
InputMethodController::~InputMethodController() = default; |
@@ -199,7 +209,6 @@ void InputMethodController::clear() { |
m_compositionRange->collapse(true); |
} |
document().markers().removeMarkers(DocumentMarker::Composition); |
- m_isDirty = false; |
} |
void InputMethodController::contextDestroyed() { |
@@ -269,14 +278,6 @@ bool InputMethodController::replaceComposition(const String& text) { |
if (!hasComposition()) |
return false; |
- // If the composition was set from existing text and didn't change, then |
- // there's nothing to do here (and we should avoid doing anything as that |
- // may clobber multi-node styled text). |
- if (!m_isDirty && composingText() == text) { |
- clear(); |
- return true; |
- } |
- |
// Select the text that will be deleted or replaced. |
selectComposition(); |
@@ -289,13 +290,19 @@ bool InputMethodController::replaceComposition(const String& text) { |
// If text is empty, then delete the old composition here. If text is |
// non-empty, InsertTextCommand::input will delete the old composition with |
// an optimized replace operation. |
- if (text.isEmpty()) |
+ if (text.isEmpty()) { |
TypingCommand::deleteSelection(document(), 0); |
+ // TODO(xiaochengh): The use of updateStyleAndLayoutIgnorePendingStylesheets |
+ // needs to be audited. see http://crbug.com/590369 for more details. |
+ document().updateStyleAndLayoutIgnorePendingStylesheets(); |
+ } |
+ |
+ const String& composing = composingText(); |
clear(); |
- insertTextDuringCompositionWithEvents( |
- frame(), text, 0, |
+ insertIncrementtalTextDuringCompositionWithEvents( |
+ frame(), composing, text, 0, |
TypingCommand::TextCompositionType::TextCompositionConfirm); |
// Event handler might destroy document. |
if (!isAvailable()) |
@@ -379,8 +386,8 @@ void InputMethodController::cancelComposition() { |
InputEvent::InputType::DeleteComposedCharacterBackward, nullAtom, |
InputEvent::EventCancelable::NotCancelable); |
dispatchCompositionUpdateEvent(frame(), emptyString()); |
- insertTextDuringCompositionWithEvents( |
- frame(), emptyString(), 0, |
+ insertIncrementtalTextDuringCompositionWithEvents( |
+ frame(), emptyString(), emptyString(), 0, |
TypingCommand::TextCompositionType::TextCompositionCancel); |
// Event handler might destroy document. |
if (!isAvailable()) |
@@ -410,28 +417,6 @@ void InputMethodController::cancelCompositionIfSelectionIsInvalid() { |
frame().chromeClient().didCancelCompositionOnSelectionChange(); |
} |
-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) { |
@@ -445,23 +430,6 @@ static size_t computeDistanceToLeftGraphemeBoundary(const Position& position) { |
adjustedPosition.computeOffsetInContainerNode()); |
} |
-static size_t computeCommonGraphemeClusterPrefixLengthForSetComposition( |
- 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) { |
@@ -475,108 +443,6 @@ static size_t computeDistanceToRightGraphemeBoundary(const Position& position) { |
position.computeOffsetInContainerNode()); |
} |
-static size_t computeCommonGraphemeClusterSuffixLengthForSetComposition( |
- 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; |
-} |
- |
-void InputMethodController::setCompositionWithIncrementalText( |
- const String& text, |
- const Vector<CompositionUnderline>& underlines, |
- int selectionStart, |
- int selectionEnd) { |
- Element* editable = frame().selection().rootEditableElement(); |
- if (!editable) |
- return; |
- |
- DCHECK_LE(selectionStart, selectionEnd); |
- String composing = composingText(); |
- const size_t commonPrefixLength = |
- computeCommonGraphemeClusterPrefixLengthForSetComposition(composing, text, |
- editable); |
- |
- // We should ignore common prefix when finding common suffix. |
- const size_t commonSuffixLength = |
- computeCommonGraphemeClusterSuffixLengthForSetComposition( |
- composing.right(composing.length() - commonPrefixLength), |
- text.right(text.length() - commonPrefixLength), editable); |
- |
- const bool inserting = |
- text.length() > commonPrefixLength + commonSuffixLength; |
- const bool deleting = |
- composing.length() > commonPrefixLength + commonSuffixLength; |
- |
- if (inserting || deleting) { |
- // Select the text to be deleted. |
- const size_t compositionStart = |
- PlainTextRange::create(*editable, compositionEphemeralRange()).start(); |
- const size_t deletionStart = compositionStart + commonPrefixLength; |
- const size_t deletionEnd = |
- compositionStart + composing.length() - commonSuffixLength; |
- const EphemeralRange& deletionRange = |
- PlainTextRange(deletionStart, deletionEnd).createRange(*editable); |
- VisibleSelection selection; |
- selection.setWithoutValidation(deletionRange.startPosition(), |
- deletionRange.endPosition()); |
- Document& currentDocument = document(); |
- frame().selection().setSelection(selection, 0); |
- clear(); |
- |
- // FrameSeleciton::setSelection() can change document associate to |frame|. |
- if (!isAvailable() || currentDocument != document()) |
- return; |
- if (!currentDocument.focusedElement()) |
- return; |
- |
- // Insert the incremental text. |
- const size_t insertionLength = |
- text.length() - commonPrefixLength - commonSuffixLength; |
- const String& insertingText = |
- text.substring(commonPrefixLength, insertionLength); |
- insertTextDuringCompositionWithEvents(frame(), insertingText, |
- TypingCommand::PreventSpellChecking, |
- TypingCommand::TextCompositionUpdate); |
- |
- // Event handlers might destroy document. |
- if (!isAvailable() || currentDocument != document()) |
- return; |
- |
- // TODO(yosin): The use of updateStyleAndLayoutIgnorePendingStylesheets |
- // needs to be audited. see http://crbug.com/590369 for more details. |
- document().updateStyleAndLayoutIgnorePendingStylesheets(); |
- |
- // Now recreate the composition starting at its original start, and |
- // apply the specified final selection offsets. |
- setCompositionFromExistingText(underlines, compositionStart, |
- compositionStart + text.length()); |
- } |
- |
- selectComposition(); |
- |
- // TODO(xiaochengh): The use of updateStyleAndLayoutIgnorePendingStylesheets |
- // needs to be audited. see http://crbug.com/590369 for more details. |
- document().updateStyleAndLayoutIgnorePendingStylesheets(); |
- |
- const PlainTextRange& selectedRange = createSelectionRangeForSetComposition( |
- selectionStart, selectionEnd, text.length()); |
- // We shouldn't close typing in the middle of setComposition. |
- setEditableSelectionOffsets(selectedRange, NotUserTriggered); |
- m_isDirty = true; |
-} |
- |
void InputMethodController::setComposition( |
const String& text, |
const Vector<CompositionUnderline>& underlines, |
@@ -589,14 +455,6 @@ void InputMethodController::setComposition( |
// See https://bugs.webkit.org/show_bug.cgi?id=46868 |
document().updateStyleAndLayoutTree(); |
- // When the IME only wants to change a few characters at the end of the |
- // composition, only touch those characters in order to preserve rich text |
- // substructure. |
- if (hasComposition() && text.length()) { |
- return setCompositionWithIncrementalText(text, underlines, selectionStart, |
- selectionEnd); |
- } |
- |
selectComposition(); |
if (frame().selection().isNone()) |
@@ -663,10 +521,11 @@ void InputMethodController::setComposition( |
DCHECK(!text.isEmpty()); |
+ const String& composing = composingText(); |
clear(); |
- insertTextDuringCompositionWithEvents( |
- frame(), text, |
+ insertIncrementtalTextDuringCompositionWithEvents( |
+ frame(), composing, text, |
TypingCommand::SelectInsertedText | TypingCommand::PreventSpellChecking, |
TypingCommand::TextCompositionUpdate); |
// Event handlers might destroy document. |
@@ -685,20 +544,15 @@ void InputMethodController::setComposition( |
Position extent = frame().selection().extent(); |
Node* extentNode = extent.anchorNode(); |
yabinh
2016/11/25 04:49:29
baseNode and extentNode could be different anchor
|
- if (baseNode != extentNode) |
- return; |
unsigned extentOffset = extent.computeOffsetInContainerNode(); |
unsigned baseOffset = base.computeOffsetInContainerNode(); |
yabinh
2016/11/25 04:49:29
Note that the offset is relative to its anchor nod
|
- if (baseOffset + text.length() != extentOffset) |
- return; |
- m_isDirty = true; |
m_hasComposition = true; |
if (!m_compositionRange) |
m_compositionRange = Range::create(document()); |
m_compositionRange->setStart(baseNode, baseOffset); |
- m_compositionRange->setEnd(baseNode, extentOffset); |
+ m_compositionRange->setEnd(extentNode, extentOffset); |
if (baseNode->layoutObject()) |
baseNode->layoutObject()->setShouldDoFullPaintInvalidation(); |