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..89d68d2f22af91432529b53d4ce0e8e19ab4bfea 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,28 @@ 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) { |
+ // TODO(xiaochengh): The use of updateStyleAndLayoutIgnorePendingStylesheets |
+ // needs to be audited. see http://crbug.com/590369 for more details. |
+ frame->document()->updateStyleAndLayoutIgnorePendingStylesheets(); |
+ |
+ 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 +281,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; |
+ } |
+ |
+ // Do nothing if no need to delete and insert. |
+ if (selectionForInsertion.isCaret() && newText.isEmpty()) |
+ return; |
+ |
+ // TODO(xiaochengh): The use of updateStyleAndLayoutIgnorePendingStylesheets |
+ // needs to be audited. see http://crbug.com/590369 for more details. |
+ document.updateStyleAndLayoutIgnorePendingStylesheets(); |
+ |
+ 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 +325,21 @@ 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; |
+ |
+ 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,24 @@ 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; |
+ |
+ 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 +543,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) { |