Support (insertFrom|deleteBy)Composition and (insert|delete)CompositionText inputTypes for InputEvents
https://bugs.webkit.org/show_bug.cgi?id=163460
<rdar://problem/28784142>
Reviewed by Darin Adler.
Source/WebCore:
Adds basic support for the composition inputTypes in the InputEvent spec. See w3.org/TR/input-events,
github.com/w3c/input-events/issues/41 and github.com/w3c/input-events/issues/42 for more details. While input
events are fired in the correct order with respect to each other, additional work will be required to ensure
that input events are fired in the correct order with respect to composition(start|update|end) events and
textInput events. This is held off until the expected ordering of events is officially defined in the spec.
Tests: fast/events/before-input-events-prevent-insert-composition.html
fast/events/before-input-events-prevent-recomposition.html
fast/events/input-events-ime-composition.html
fast/events/input-events-ime-recomposition.html
* editing/CompositeEditCommand.cpp:
(WebCore::CompositeEditCommand::apply):
* editing/CompositeEditCommand.h:
(WebCore::CompositeEditCommand::isBeforeInputEventCancelable):
Adds a new virtual method hook for subclasses to mark their `beforeinput` events as non-cancelable (see
TypingCommand::isBeforeInputEventCancelable). By default, `beforeinput` events are cancelable.
* editing/EditAction.h:
Adds 4 new EditActions corresponding to the 4 composition-related inputTypes. These are:
EditActionTypingDeletePendingComposition => "deleteCompositionText"
EditActionTypingDeleteFinalComposition => "deleteByComposition"
EditActionTypingInsertPendingComposition => "insertCompositionText"
EditActionTypingInsertFinalComposition => "insertFromComposition"
* editing/EditCommand.cpp:
(WebCore::inputTypeNameForEditingAction):
* editing/Editor.cpp:
(WebCore::dispatchBeforeInputEvent):
(WebCore::dispatchBeforeInputEvents):
(WebCore::Editor::willApplyEditing):
(WebCore::Editor::insertTextWithoutSendingTextEvent):
(WebCore::Editor::setComposition):
In setComposition(text, mode), tweak the logic for committing a composition to always delete the selection
before inserting the final composition text. In setComposition(text, underlines, start, end), catch the case
where we're beginning to recompose an existing range in the DOM and delete the recomposed text first.
* editing/TypingCommand.cpp:
(WebCore::editActionForTypingCommand):
(WebCore::TypingCommand::TypingCommand):
(WebCore::TypingCommand::deleteSelection):
Adds a TextCompositionType parameter so that call sites (see Editor::setComposition) can indicate what state the
edited composition is in. This allows us to differentiate between deletion of finalized composition text in
preparation of recomposing a range in the DOM, and deletion of composition text that has not yet been committed
in preparation for inserting a finalized composition into the DOM.
(WebCore::TypingCommand::deleteKeyPressed):
(WebCore::TypingCommand::forwardDeleteKeyPressed):
(WebCore::TypingCommand::insertText):
(WebCore::TypingCommand::insertLineBreak):
(WebCore::TypingCommand::insertParagraphSeparatorInQuotedContent):
(WebCore::TypingCommand::insertParagraphSeparator):
(WebCore::TypingCommand::isBeforeInputEventCancelable):
(WebCore::TypingCommand::inputEventData):
(WebCore::TypingCommand::willAddTypingToOpenCommand):
* editing/TypingCommand.h:
Source/WebKit/mac:
Handle new EditAction types for inserting/deleting pending/final compositions.
* WebCoreSupport/WebEditorClient.mm:
(undoNameForEditAction):
Source/WebKit2:
Handle new EditAction types for inserting/deleting pending/final compositions.
* UIProcess/WebEditCommandProxy.cpp:
(WebKit::WebEditCommandProxy::nameForEditAction):
LayoutTests:
Adds 4 new layout tests to verify that composition events are dispatched as expected when using IME, and that
input events of type "insertFromComposition" and "deleteByComposition" can be prevented.
Also rebaselines an existing WK1 editing test (text-input-controller.html) to account for how we now delete the
existing composition text before inserting the finalized composition text in Editor::setComposition. This means
that there are a few more delegate calls than there were before (as seen in the expected output), although the
resulting behavior is still the same.
* editing/mac/input/text-input-controller-expected.txt:
* fast/events/before-input-events-prevent-insert-composition.html: Added.
* fast/events/before-input-events-prevent-recomposition.html: Added.
* fast/events/input-events-ime-composition.html: Added.
* fast/events/input-events-ime-recomposition.html: Added.
* platform/ios-simulator/TestExpectations:
git-svn-id: http://svn.webkit.org/repository/webkit/trunk@207698 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/Source/WebCore/editing/TypingCommand.cpp b/Source/WebCore/editing/TypingCommand.cpp
index d05d4f9..7561340 100644
--- a/Source/WebCore/editing/TypingCommand.cpp
+++ b/Source/WebCore/editing/TypingCommand.cpp
@@ -77,8 +77,24 @@
const String& m_text;
};
-static inline EditAction editActionForTypingCommand(TypingCommand::ETypingCommand command, TextGranularity granularity)
+static inline EditAction editActionForTypingCommand(TypingCommand::ETypingCommand command, TextGranularity granularity, TypingCommand::TextCompositionType compositionType)
{
+ if (compositionType == TypingCommand::TextCompositionPending) {
+ if (command == TypingCommand::InsertText)
+ return EditActionTypingInsertPendingComposition;
+ if (command == TypingCommand::DeleteSelection)
+ return EditActionTypingDeletePendingComposition;
+ ASSERT_NOT_REACHED();
+ }
+
+ if (compositionType == TypingCommand::TextCompositionFinal) {
+ if (command == TypingCommand::InsertText)
+ return EditActionTypingInsertFinalComposition;
+ if (command == TypingCommand::DeleteSelection)
+ return EditActionTypingDeleteFinalComposition;
+ ASSERT_NOT_REACHED();
+ }
+
switch (command) {
case TypingCommand::DeleteSelection:
return EditActionTypingDeleteSelection;
@@ -124,7 +140,7 @@
}
TypingCommand::TypingCommand(Document& document, ETypingCommand commandType, const String &textToInsert, Options options, TextGranularity granularity, TextCompositionType compositionType)
- : TextInsertionBaseCommand(document, editActionForTypingCommand(commandType, granularity))
+ : TextInsertionBaseCommand(document, editActionForTypingCommand(commandType, granularity, compositionType))
, m_commandType(commandType)
, m_textToInsert(textToInsert)
, m_currentTextToInsert(textToInsert)
@@ -142,7 +158,7 @@
updatePreservesTypingStyle(m_commandType);
}
-void TypingCommand::deleteSelection(Document& document, Options options)
+void TypingCommand::deleteSelection(Document& document, Options options, TextCompositionType compositionType)
{
Frame* frame = document.frame();
ASSERT(frame);
@@ -151,12 +167,13 @@
return;
if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(*frame)) {
+ lastTypingCommand->setCompositionType(compositionType);
lastTypingCommand->setShouldPreventSpellChecking(options & PreventSpellChecking);
lastTypingCommand->deleteSelection(options & SmartDelete);
return;
}
- TypingCommand::create(document, DeleteSelection, emptyString(), options)->apply();
+ TypingCommand::create(document, DeleteSelection, emptyString(), options, compositionType)->apply();
}
void TypingCommand::deleteKeyPressed(Document& document, Options options, TextGranularity granularity)
@@ -164,6 +181,7 @@
if (granularity == CharacterGranularity) {
if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(*document.frame())) {
updateSelectionIfDifferentFromCurrentSelection(lastTypingCommand.get(), document.frame());
+ lastTypingCommand->setCompositionType(TextCompositionNone);
lastTypingCommand->setShouldPreventSpellChecking(options & PreventSpellChecking);
lastTypingCommand->deleteKeyPressed(granularity, options & AddsToKillRing);
return;
@@ -180,6 +198,7 @@
if (granularity == CharacterGranularity) {
if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(*frame)) {
updateSelectionIfDifferentFromCurrentSelection(lastTypingCommand.get(), frame);
+ lastTypingCommand->setCompositionType(TextCompositionNone);
lastTypingCommand->setShouldPreventSpellChecking(options & PreventSpellChecking);
lastTypingCommand->forwardDeleteKeyPressed(granularity, options & AddsToKillRing);
return;
@@ -221,7 +240,7 @@
VisibleSelection currentSelection = frame->selection().selection();
- String newText = dispatchBeforeTextInsertedEvent(text, selectionForInsertion, compositionType == TextCompositionUpdate);
+ String newText = dispatchBeforeTextInsertedEvent(text, selectionForInsertion, compositionType == TextCompositionPending);
// 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
@@ -246,6 +265,7 @@
void TypingCommand::insertLineBreak(Document& document, Options options)
{
if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(*document.frame())) {
+ lastTypingCommand->setCompositionType(TextCompositionNone);
lastTypingCommand->setShouldRetainAutocorrectionIndicator(options & RetainAutocorrectionIndicator);
lastTypingCommand->insertLineBreakAndNotifyAccessibility();
return;
@@ -257,6 +277,7 @@
void TypingCommand::insertParagraphSeparatorInQuotedContent(Document& document)
{
if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(*document.frame())) {
+ lastTypingCommand->setCompositionType(TextCompositionNone);
lastTypingCommand->insertParagraphSeparatorInQuotedContentAndNotifyAccessibility();
return;
}
@@ -267,6 +288,7 @@
void TypingCommand::insertParagraphSeparator(Document& document, Options options)
{
if (RefPtr<TypingCommand> lastTypingCommand = lastTypingCommandIfStillOpenForTyping(*document.frame())) {
+ lastTypingCommand->setCompositionType(TextCompositionNone);
lastTypingCommand->setShouldRetainAutocorrectionIndicator(options & RetainAutocorrectionIndicator);
lastTypingCommand->insertParagraphSeparatorAndNotifyAccessibility();
return;
@@ -367,12 +389,21 @@
return inputTypeNameForEditingAction(m_currentTypingEditAction);
}
+bool TypingCommand::isBeforeInputEventCancelable() const
+{
+ return m_currentTypingEditAction != EditActionTypingInsertPendingComposition && m_currentTypingEditAction != EditActionTypingDeletePendingComposition;
+}
+
String TypingCommand::inputEventData() const
{
- if (m_currentTypingEditAction == EditActionTypingInsertText)
+ switch (m_currentTypingEditAction) {
+ case EditActionTypingInsertText:
+ case EditActionTypingInsertPendingComposition:
+ case EditActionTypingInsertFinalComposition:
return m_currentTextToInsert;
-
- return CompositeEditCommand::inputEventData();
+ default:
+ return CompositeEditCommand::inputEventData();
+ }
}
void TypingCommand::didApplyCommand()
@@ -438,7 +469,7 @@
bool TypingCommand::willAddTypingToOpenCommand(ETypingCommand commandType, TextGranularity granularity, const String& text, RefPtr<Range>&& range)
{
m_currentTextToInsert = text;
- m_currentTypingEditAction = editActionForTypingCommand(commandType, granularity);
+ m_currentTypingEditAction = editActionForTypingCommand(commandType, granularity, m_compositionType);
if (!shouldDeferWillApplyCommandUntilAddingTypingCommand())
return true;