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..f7d1999c37633d9080564b64f763cc24f7ecee39 100644 |
| --- a/third_party/WebKit/Source/core/editing/commands/TypingCommand.cpp |
| +++ b/third_party/WebKit/Source/core/editing/commands/TypingCommand.cpp |
| @@ -31,21 +31,99 @@ |
| #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" |
| #include "core/editing/commands/BreakBlockquoteCommand.h" |
| +#include "core/editing/commands/InsertIncrementalTextCommand.h" |
| #include "core/editing/commands/InsertLineBreakCommand.h" |
| #include "core/editing/commands/InsertParagraphSeparatorCommand.h" |
| #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" |
| namespace blink { |
| +namespace { |
| + |
| +String dispatchBeforeTextInsertedEvent(const String& text, |
| + const VisibleSelection& selection) { |
| + String newText = text; |
| + if (Node* startNode = selection.start().computeContainerNode()) { |
| + if (rootEditableElement(*startNode)) { |
| + // Send BeforeTextInsertedEvent. The event handler will update text if |
| + // necessary. |
| + BeforeTextInsertedEvent* evt = BeforeTextInsertedEvent::create(text); |
| + rootEditableElement(*startNode)->dispatchEvent(evt); |
| + newText = evt->text(); |
| + } |
| + } |
| + return newText; |
| +} |
| + |
| +DispatchEventResult dispatchTextInputEvent(LocalFrame* frame, |
| + const String& text) { |
| + if (Element* target = frame->document()->focusedElement()) { |
| + // Send TextInputEvent. Unlike BeforeTextInsertedEvent, there is no need to |
| + // update text for TextInputEvent as it doesn't have the API to modify text. |
| + TextEvent* event = TextEvent::create(frame->domWindow(), text, |
| + TextEventInputIncrementalInsertion); |
| + event->setUnderlyingEvent(nullptr); |
| + return target->dispatchEvent(event); |
| + } |
| + return DispatchEventResult::CanceledBeforeDispatch; |
| +} |
| + |
| +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); |
| +} |
| + |
| +VisibleSelection createSelection(const size_t start, |
| + const size_t end, |
| + const bool isDirectional, |
| + Element* 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(); |
| + |
| + const VisibleSelection& selection = |
| + createVisibleSelection(SelectionInDOMTree::Builder() |
| + .setBaseAndExtent(startPosition, endPosition) |
| + .setIsDirectional(isDirectional) |
| + .build()); |
| + return selection; |
| +} |
| + |
| +bool canAppendNewLineFeedToSelection(const VisibleSelection& selection) { |
| + Element* element = selection.rootEditableElement(); |
| + if (!element) |
| + return false; |
| + |
| + BeforeTextInsertedEvent* event = |
| + BeforeTextInsertedEvent::create(String("\n")); |
| + element->dispatchEvent(event); |
| + return event->text().length(); |
| +} |
| + |
| +} // anonymous namespace |
| + |
| using namespace HTMLNames; |
| TypingCommand::TypingCommand(Document& document, |
| @@ -143,7 +221,7 @@ void TypingCommand::forwardDeleteKeyPressed(Document& document, |
| } |
| String TypingCommand::textDataForInputEvent() const { |
| - if (m_commands.isEmpty()) |
| + if (m_commands.isEmpty() || isIncrementalInsertion()) |
| return m_textToInsert; |
| return m_commands.back()->textDataForInputEvent(); |
| } |
| @@ -160,30 +238,11 @@ void TypingCommand::updateSelectionIfDifferentFromCurrentSelection( |
| typingCommand->setEndingVisibleSelection(currentSelection); |
| } |
| -static String dispatchBeforeTextInsertedEvent( |
| - const String& text, |
| - const VisibleSelection& selectionForInsertion, |
| - bool insertionIsForUpdatingComposition) { |
| - if (insertionIsForUpdatingComposition) |
| - return text; |
| - |
| - String newText = text; |
| - if (Node* startNode = selectionForInsertion.start().computeContainerNode()) { |
| - if (rootEditableElement(*startNode)) { |
| - // Send BeforeTextInsertedEvent. The event handler will update text if |
| - // necessary. |
| - BeforeTextInsertedEvent* evt = BeforeTextInsertedEvent::create(text); |
| - rootEditableElement(*startNode)->dispatchEvent(evt); |
| - newText = evt->text(); |
| - } |
| - } |
| - return newText; |
| -} |
| - |
| void TypingCommand::insertText(Document& document, |
| const String& text, |
| Options options, |
| - TextCompositionType composition) { |
| + TextCompositionType composition, |
| + const bool isIncrementalInsertion) { |
| LocalFrame* frame = document.frame(); |
| DCHECK(frame); |
| @@ -192,7 +251,24 @@ void TypingCommand::insertText(Document& document, |
| isSpaceOrNewline(text[0])); |
| insertText(document, text, frame->selection().selection(), options, |
| - composition); |
| + composition, isIncrementalInsertion); |
| +} |
| + |
| +void TypingCommand::adjustSelectionAfterIncrementalInsertion( |
| + TypingCommand* command, |
| + LocalFrame* frame, |
| + const size_t start, |
| + const size_t end) { |
| + Element* element = frame->selection().selection().rootEditableElement(); |
| + DCHECK(element); |
| + |
| + const VisibleSelection& selection = createSelection( |
| + start, end, command->endingSelection().isDirectional(), element); |
| + |
| + if (selection != frame->selection().selection()) { |
| + command->setEndingVisibleSelection(selection); |
| + frame->selection().setSelection(selection); |
| + } |
| } |
| // FIXME: We shouldn't need to take selectionForInsertion. It should be |
| @@ -201,14 +277,32 @@ void TypingCommand::insertText(Document& document, |
| const String& text, |
| const VisibleSelection& selectionForInsertion, |
| Options options, |
| - TextCompositionType compositionType) { |
| + TextCompositionType compositionType, |
| + const bool isIncrementalInsertion) { |
| LocalFrame* frame = document.frame(); |
| DCHECK(frame); |
| VisibleSelection currentSelection = frame->selection().selection(); |
| - String newText = dispatchBeforeTextInsertedEvent( |
| - text, selectionForInsertion, compositionType == TextCompositionUpdate); |
| + String newText = text; |
| + if (compositionType != TextCompositionUpdate) |
| + newText = dispatchBeforeTextInsertedEvent(text, selectionForInsertion); |
| + |
| + if (compositionType == TextCompositionConfirm) { |
| + if (dispatchTextInputEvent(frame, newText) != |
| + DispatchEventResult::NotCanceled) |
| + return; |
| + } |
| + |
| + // TODO(xiaochengh): The use of updateStyleAndLayoutIgnorePendingStylesheets |
| + // needs to be audited. see http://crbug.com/590369 for more details. |
| + document.updateStyleAndLayoutIgnorePendingStylesheets(); |
|
Xiaocheng
2016/12/09 05:10:30
Please move it to just before calling |getSelectio
yabinh
2016/12/09 06:16:47
Done.
|
| + |
| + // Do nothing if no need to delete and insert. |
| + if (selectionForInsertion.isCaret() && newText.isEmpty()) |
| + return; |
| + |
|
yabinh
2016/12/08 07:54:58
Note that the DOM will change after insertion, so
|
| + const PlainTextRange selectionOffsets = getSelectionOffsets(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 |
| @@ -227,9 +321,25 @@ void TypingCommand::insertText(Document& document, |
| lastTypingCommand->setShouldPreventSpellChecking(options & |
| PreventSpellChecking); |
| EditingState editingState; |
| + lastTypingCommand->m_isIncrementalInsertion = isIncrementalInsertion; |
| lastTypingCommand->insertText(newText, options & SelectInsertedText, |
| &editingState); |
| - // Nothing to do even if the command was aborted. |
| + |
| + if (editingState.isAborted()) |
| + return; |
| + |
| + // TODO(xiaochengh): The use of updateStyleAndLayoutIgnorePendingStylesheets |
| + // needs to be audited. see http://crbug.com/590369 for more details. |
| + document.updateStyleAndLayoutIgnorePendingStylesheets(); |
|
Xiaocheng
2016/12/09 05:10:30
Please move it into adjustSelectionAfterIncrementa
yabinh
2016/12/09 06:16:47
Done.
|
| + |
| + if (isIncrementalInsertion) { |
| + const size_t newEnd = selectionOffsets.start() + newText.length(); |
| + const size_t newStart = (compositionType == TextCompositionUpdate) |
| + ? selectionOffsets.start() |
| + : newEnd; |
| + adjustSelectionAfterIncrementalInsertion(lastTypingCommand, frame, |
| + newStart, newEnd); |
| + } |
| return; |
| } |
| @@ -240,10 +350,28 @@ void TypingCommand::insertText(Document& document, |
| command->setStartingSelection(selectionForInsertion); |
| command->setEndingVisibleSelection(selectionForInsertion); |
| } |
| - command->apply(); |
| + command->m_isIncrementalInsertion = isIncrementalInsertion; |
| + const bool aborted = !(command->apply()); |
| + |
| if (changeSelection) { |
| command->setEndingVisibleSelection(currentSelection); |
| frame->selection().setSelection(currentSelection); |
| + return; |
| + } |
| + |
| + if (aborted) |
| + return; |
| + |
| + // TODO(xiaochengh): The use of updateStyleAndLayoutIgnorePendingStylesheets |
| + // needs to be audited. see http://crbug.com/590369 for more details. |
| + document.updateStyleAndLayoutIgnorePendingStylesheets(); |
|
Xiaocheng
2016/12/09 05:10:30
Please move it into adjustSelectionAfterIncrementa
yabinh
2016/12/09 06:16:47
Done.
|
| + |
| + if (isIncrementalInsertion) { |
| + const size_t newEnd = selectionOffsets.start() + newText.length(); |
| + const size_t newStart = (compositionType == TextCompositionUpdate) |
| + ? selectionOffsets.start() |
| + : newEnd; |
| + adjustSelectionAfterIncrementalInsertion(command, frame, newStart, newEnd); |
| } |
| } |
| @@ -419,35 +547,37 @@ void TypingCommand::insertText(const String& text, |
| return; |
| } |
| - if (text.length() > offset) |
| + if (text.length() > offset) { |
| insertTextRunWithoutNewlines(text.substring(offset, text.length() - offset), |
| selectInsertedText, editingState); |
| + } |
| } |
| void TypingCommand::insertTextRunWithoutNewlines(const String& text, |
| bool selectInsertedText, |
| EditingState* editingState) { |
| - InsertTextCommand* command = InsertTextCommand::create( |
| - document(), text, selectInsertedText, |
| - m_compositionType == TextCompositionNone |
| - ? InsertTextCommand::RebalanceLeadingAndTrailingWhitespaces |
| - : InsertTextCommand::RebalanceAllWhitespaces); |
| + CompositeEditCommand* command; |
| + if (isIncrementalInsertion()) { |
| + command = InsertIncrementalTextCommand::create( |
| + document(), text, selectInsertedText, |
| + m_compositionType == TextCompositionNone |
| + ? InsertIncrementalTextCommand:: |
| + RebalanceLeadingAndTrailingWhitespaces |
| + : InsertIncrementalTextCommand::RebalanceAllWhitespaces); |
| + } else { |
| + command = InsertTextCommand::create( |
| + document(), text, selectInsertedText, |
| + m_compositionType == TextCompositionNone |
| + ? InsertTextCommand::RebalanceLeadingAndTrailingWhitespaces |
| + : InsertTextCommand::RebalanceAllWhitespaces); |
| + } |
| applyCommandToComposite(command, endingSelection(), editingState); |
| if (editingState->isAborted()) |
| return; |
| - typingAddedToOpenCommand(InsertText); |
| -} |
| - |
| -static bool canAppendNewLineFeedToSelection(const VisibleSelection& selection) { |
| - Element* element = selection.rootEditableElement(); |
| - if (!element) |
| - return false; |
| - BeforeTextInsertedEvent* event = |
| - BeforeTextInsertedEvent::create(String("\n")); |
| - element->dispatchEvent(event); |
| - return event->text().length(); |
| + m_textToInsert = text; |
| + typingAddedToOpenCommand(InsertText); |
| } |
| void TypingCommand::insertLineBreak(EditingState* editingState) { |