Chromium Code Reviews| Index: Source/core/editing/InputMethodController.cpp |
| diff --git a/Source/core/editing/InputMethodController.cpp b/Source/core/editing/InputMethodController.cpp |
| index 3c695f1e8270c7f1baf763f8e3825d031e9a2993..c91b92cf248149decd60eaa8dd1420acfcbedb41 100644 |
| --- a/Source/core/editing/InputMethodController.cpp |
| +++ b/Source/core/editing/InputMethodController.cpp |
| @@ -29,8 +29,8 @@ |
| #include "core/dom/Document.h" |
| #include "core/dom/Element.h" |
| -#include "core/dom/Range.h" |
| #include "core/dom/Text.h" |
| +#include "core/editing/EditingUtilities.h" |
| #include "core/editing/Editor.h" |
| #include "core/editing/commands/TypingCommand.h" |
| #include "core/events/CompositionEvent.h" |
| @@ -62,8 +62,7 @@ PassOwnPtrWillBeRawPtr<InputMethodController> InputMethodController::create(Loca |
| InputMethodController::InputMethodController(LocalFrame& frame) |
| : m_frame(&frame) |
| - , m_compositionStart(0) |
| - , m_compositionEnd(0) |
| + , m_isExistingText(true) |
|
yosin_UTC9
2015/08/31 01:35:40
Can we use inverse logic? It is unclear why just i
aelias_OOO_until_Jul13
2015/09/04 04:03:24
Done.
|
| { |
| } |
| @@ -73,7 +72,7 @@ InputMethodController::~InputMethodController() |
| bool InputMethodController::hasComposition() const |
| { |
| - return m_compositionNode && m_compositionNode->isContentEditable(); |
| + return m_compositionRange.get() && m_compositionRange->startContainer() && m_compositionRange->startContainer()->isContentEditable(); |
| } |
| inline Editor& InputMethodController::editor() const |
| @@ -83,8 +82,9 @@ inline Editor& InputMethodController::editor() const |
| void InputMethodController::clear() |
| { |
| - m_compositionNode = nullptr; |
| + m_compositionRange = nullptr; |
| m_customCompositionUnderlines.clear(); |
| + m_isExistingText = true; |
| } |
| bool InputMethodController::insertTextForConfirmedComposition(const String& text) |
| @@ -109,7 +109,7 @@ bool InputMethodController::confirmComposition() |
| { |
| if (!hasComposition()) |
| return false; |
| - return finishComposition(m_compositionNode->data().substring(m_compositionStart, m_compositionEnd - m_compositionStart), ConfirmComposition); |
| + return confirmComposition(plainText(compositionEphemeralRange())); |
| } |
| bool InputMethodController::confirmComposition(const String& text) |
| @@ -149,13 +149,16 @@ void InputMethodController::cancelCompositionIfSelectionIsInvalid() |
| return; |
| // Check if selection start and selection end are valid. |
| - Position start = frame().selection().start(); |
| - Position end = frame().selection().end(); |
| - if (start.computeContainerNode() == m_compositionNode |
| - && end.computeContainerNode() == m_compositionNode |
| - && static_cast<unsigned>(start.computeOffsetInContainerNode()) >= m_compositionStart |
| - && static_cast<unsigned>(end.computeOffsetInContainerNode()) <= m_compositionEnd) |
| - return; |
| + FrameSelection& selection = frame().selection(); |
| + if (!selection.isNone()) { |
| + Position start = selection.start(); |
| + Position end = selection.end(); |
| + if (start.anchorNode() && end.anchorNode()) { |
| + if (m_compositionRange->isPointInRange(start.computeContainerNode(), start.computeOffsetInContainerNode(), IGNORE_EXCEPTION) |
| + && m_compositionRange->isPointInRange(end.computeContainerNode(), end.computeOffsetInContainerNode(), IGNORE_EXCEPTION)) |
| + return; |
| + } |
| + } |
| cancelComposition(); |
| frame().chromeClient().didCancelCompositionOnSelectionChange(); |
| @@ -186,17 +189,19 @@ bool InputMethodController::finishComposition(const String& text, FinishComposit |
| target->dispatchEvent(event); |
| } |
| + bool existingText = m_isExistingText; |
| + |
| // 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() && mode != CancelComposition) { |
| + if (text.isEmpty() && mode != CancelComposition && !existingText) { |
| ASSERT(frame().document()); |
| TypingCommand::deleteSelection(*frame().document(), 0); |
| } |
| - m_compositionNode = nullptr; |
| - m_customCompositionUnderlines.clear(); |
| + clear(); |
| - insertTextForConfirmedComposition(text); |
| + if (!existingText) |
| + insertTextForConfirmedComposition(text); |
| if (mode == CancelComposition) { |
| // An open typing command that disagrees about current selection would cause issues with typing later on. |
| @@ -227,15 +232,15 @@ void InputMethodController::setComposition(const String& text, const Vector<Comp |
| // 1. Starting a new composition. |
| // Send a compositionstart and a compositionupdate event when this function creates |
| // a new composition node, i.e. |
| - // m_compositionNode == 0 && !text.isEmpty(). |
| + // !hasComposition() && !text.isEmpty(). |
| // Sending a compositionupdate event at this time ensures that at least one |
| // compositionupdate event is dispatched. |
| // 2. Updating the existing composition node. |
| // Send a compositionupdate event when this function updates the existing composition |
| - // node, i.e. m_compositionNode != 0 && !text.isEmpty(). |
| + // node, i.e. hasComposition() && !text.isEmpty(). |
| // 3. Canceling the ongoing composition. |
| // Send a compositionend event when function deletes the existing composition node, i.e. |
| - // m_compositionNode != 0 && test.isEmpty(). |
| + // !hasComposition() && test.isEmpty(). |
| RefPtrWillBeRawPtr<CompositionEvent> event = nullptr; |
| if (!hasComposition()) { |
| // We should send a compositionstart event only when the given text is not empty because this |
| @@ -261,8 +266,9 @@ void InputMethodController::setComposition(const String& text, const Vector<Comp |
| TypingCommand::deleteSelection(*frame().document(), TypingCommand::PreventSpellChecking); |
| } |
| - m_compositionNode = nullptr; |
| - m_customCompositionUnderlines.clear(); |
| + clear(); |
| + |
| + m_isExistingText = false; |
| if (text.isEmpty()) |
| return; |
| @@ -285,17 +291,25 @@ void InputMethodController::setComposition(const String& text, const Vector<Comp |
| if (baseOffset + text.length() != extentOffset) |
| return; |
| - m_compositionNode = toText(baseNode); |
| - m_compositionStart = baseOffset; |
| - m_compositionEnd = extentOffset; |
| - m_customCompositionUnderlines = underlines; |
| - for (auto& underline : m_customCompositionUnderlines) { |
| + m_compositionRange = Range::create(baseNode->document(), baseNode, baseOffset, baseNode, extentOffset); |
| + NodeUnderlinesMap customCompositionUnderlines; |
| + customCompositionUnderlines[baseNode].first = baseNode; |
| + customCompositionUnderlines[baseNode].second = underlines; |
| + for (auto& underline : customCompositionUnderlines[baseNode].second) { |
| underline.startOffset += baseOffset; |
| underline.endOffset += baseOffset; |
| } |
| + |
| + m_customCompositionUnderlines.swap(customCompositionUnderlines); |
| + |
| if (baseNode->layoutObject()) |
| baseNode->layoutObject()->setShouldDoFullPaintInvalidation(); |
| + for (auto& nodeUnderlinesPair : customCompositionUnderlines) { |
| + if (nodeUnderlinesPair.first->layoutObject() && nodeUnderlinesPair.first != baseNode) |
| + nodeUnderlinesPair.first->layoutObject()->setShouldDoFullPaintInvalidation(); |
| + } |
| + |
| unsigned start = std::min(baseOffset + selectionStart, extentOffset); |
| unsigned end = std::min(std::max(start, baseOffset + selectionEnd), extentOffset); |
| RefPtrWillBeRawPtr<Range> selectedRange = Range::create(baseNode->document(), baseNode, start, baseNode, end); |
| @@ -305,56 +319,99 @@ void InputMethodController::setComposition(const String& text, const Vector<Comp |
| void InputMethodController::setCompositionFromExistingText(const Vector<CompositionUnderline>& underlines, unsigned compositionStart, unsigned compositionEnd) |
| { |
| Element* editable = frame().selection().rootEditableElement(); |
| - Position base = mostForwardCaretPosition(frame().selection().base()); |
| - Node* baseNode = base.anchorNode(); |
| - if (baseNode && editable->firstChild() == baseNode && editable->lastChild() == baseNode && baseNode->isTextNode()) { |
| - m_compositionNode = nullptr; |
| - m_customCompositionUnderlines.clear(); |
| + if (!editable) |
| + return; |
| - if (!base.isOffsetInAnchor()) |
| - return; |
| - if (baseNode != frame().selection().extent().anchorNode()) |
| - return; |
| + EphemeralRange range = PlainTextRange(compositionStart, compositionEnd).createRange(*editable); |
| + if (range.isNull()) |
| + return; |
| - m_compositionNode = toText(baseNode); |
| - const EphemeralRange range = PlainTextRange(compositionStart, compositionEnd).createRange(*editable); |
| - if (range.isNull()) |
| - return; |
| + Position start = range.startPosition(); |
| + if (!start.anchorNode() || start.anchorNode()->rootEditableElement() != editable) |
| + return; |
| - m_compositionStart = range.startPosition().computeOffsetInContainerNode(); |
| - m_compositionEnd = range.endPosition().computeOffsetInContainerNode(); |
| - m_customCompositionUnderlines = underlines; |
| - size_t numUnderlines = m_customCompositionUnderlines.size(); |
| - for (size_t i = 0; i < numUnderlines; ++i) { |
| - m_customCompositionUnderlines[i].startOffset += m_compositionStart; |
| - m_customCompositionUnderlines[i].endOffset += m_compositionStart; |
| - } |
| - if (baseNode->layoutObject()) |
| - baseNode->layoutObject()->setShouldDoFullPaintInvalidation(); |
| + Position end = range.endPosition(); |
| + if (!end.anchorNode() || end.anchorNode()->rootEditableElement() != editable) |
| return; |
| + |
| + clear(); |
| + |
| + // customCompositionUnderlines has the same type as m_customCompositionUnderlines. So we can swap them later. |
| + NodeUnderlinesMap customCompositionUnderlines; |
| + CompositionUnderline underline; |
| + EphemeralRange ephemeralLineRange; |
| + RefPtrWillBeRawPtr<Range> underlineRange; |
| + for (size_t i = 0; i < underlines.size(); ++i) { |
| + // We separate each disjoint underline into node specific underlines and associate them with the node that they belong to. |
| + ephemeralLineRange = PlainTextRange(compositionStart + underlines[i].startOffset, compositionStart + underlines[i].endOffset).createRange(*editable); |
| + if (ephemeralLineRange.isNull()) |
| + continue; |
| + underlineRange = Range::create(ephemeralLineRange.document(), ephemeralLineRange.startPosition(), ephemeralLineRange.endPosition()); |
| + Node* stopNode = underlineRange->pastLastNode(); |
| + for (Node* node = underlineRange->firstNode(); node && node != stopNode; node = NodeTraversal::next(*node)) { |
| + if (node->isTextNode()) { |
| + if (!customCompositionUnderlines[node].first) |
| + customCompositionUnderlines[node].first = node; |
| + underline = underlines[i]; |
| + underline.startOffset = node == underlineRange->startContainer() ? underlineRange->startOffset() : 0; |
| + underline.endOffset = node == underlineRange->endContainer() ? underlineRange->endOffset() : toText(node)->length(); |
| + customCompositionUnderlines[node].second.append(underline); |
| + } |
| + } |
| } |
| - Editor::RevealSelectionScope revealSelectionScope(&editor()); |
| - SelectionOffsetsScope selectionOffsetsScope(this); |
| - setSelectionOffsets(PlainTextRange(compositionStart, compositionEnd)); |
| - setComposition(frame().selectedText(), underlines, 0, 0); |
| + m_compositionRange = Range::create(range.document(), range.startPosition(), range.endPosition()); |
| + m_customCompositionUnderlines.swap(customCompositionUnderlines); |
| + |
| + // Invalidate affected composition nodes |
| + for (auto& nodeUnderlinesPair : m_customCompositionUnderlines) { |
| + if (nodeUnderlinesPair.first->layoutObject()) |
| + nodeUnderlinesPair.first->layoutObject()->setShouldDoFullPaintInvalidation(); |
| + NodeUnderlinesMap::iterator it = customCompositionUnderlines.find(nodeUnderlinesPair.first); |
| + if (it != customCompositionUnderlines.end()) |
| + customCompositionUnderlines.erase(it); |
| + } |
| + |
| + for (auto& nodeUnderlinesPair : customCompositionUnderlines) { |
| + if (nodeUnderlinesPair.first->layoutObject()) |
| + nodeUnderlinesPair.first->layoutObject()->setShouldDoFullPaintInvalidation(); |
| + } |
| } |
| EphemeralRange InputMethodController::compositionEphemeralRange() const |
| { |
| if (!hasComposition()) |
| return EphemeralRange(); |
| - unsigned length = m_compositionNode->length(); |
| - unsigned start = std::min(m_compositionStart, length); |
| - unsigned end = std::min(std::max(start, m_compositionEnd), length); |
| - if (start >= end) |
| - return EphemeralRange(); |
| - return EphemeralRange(Position(m_compositionNode.get(), start), Position(m_compositionNode.get(), end)); |
| + return EphemeralRange(m_compositionRange->startPosition(), m_compositionRange->endPosition()); |
| } |
| PassRefPtrWillBeRawPtr<Range> InputMethodController::compositionRange() const |
| { |
| - return createRange(compositionEphemeralRange()); |
| + return hasComposition() ? m_compositionRange : nullptr; |
| +} |
| + |
| +bool InputMethodController::isCompositionNode(const Node* node) |
| +{ |
| + if (!node || m_customCompositionUnderlines.empty() || !node->isTextNode()) |
| + return false; |
| + |
| + return m_customCompositionUnderlines.find(node) != m_customCompositionUnderlines.end(); |
| +} |
| + |
| +const Vector<CompositionUnderline>& InputMethodController::customCompositionUnderlines() const |
| +{ |
| + CR_DEFINE_STATIC_LOCAL(Vector<CompositionUnderline>, emptyUnderline, ()); |
| + return m_customCompositionUnderlines.empty() ? emptyUnderline : m_customCompositionUnderlines.begin()->second.second; |
| +} |
| + |
| +const Vector<CompositionUnderline>* InputMethodController::customCompositionUnderlines(const Node* node) const |
| +{ |
| + if (!node || !node->isTextNode() || m_customCompositionUnderlines.empty()) |
| + return nullptr; |
| + |
| + NodeUnderlinesMap::iterator it = m_customCompositionUnderlines.find(node); |
| + return (it == m_customCompositionUnderlines.end() || m_customCompositionUnderlines[node].second.isEmpty()) ? |
| + nullptr : &(m_customCompositionUnderlines[node].second); |
| } |
| PlainTextRange InputMethodController::getSelectionOffsets() const |
| @@ -422,7 +479,7 @@ void InputMethodController::extendSelectionAndDelete(int before, int after) |
| DEFINE_TRACE(InputMethodController) |
| { |
| visitor->trace(m_frame); |
| - visitor->trace(m_compositionNode); |
| + visitor->trace(m_compositionRange); |
| } |
| } // namespace blink |