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/CompositeEditCommand.cpp b/Source/WebCore/editing/CompositeEditCommand.cpp
index e346000..f8c428f 100644
--- a/Source/WebCore/editing/CompositeEditCommand.cpp
+++ b/Source/WebCore/editing/CompositeEditCommand.cpp
@@ -334,9 +334,13 @@
         case EditActionTypingDeleteWordForward:
         case EditActionTypingDeleteLineBackward:
         case EditActionTypingDeleteLineForward:
+        case EditActionTypingDeletePendingComposition:
+        case EditActionTypingDeleteFinalComposition:
         case EditActionTypingInsertText:
         case EditActionTypingInsertLineBreak:
         case EditActionTypingInsertParagraph:
+        case EditActionTypingInsertPendingComposition:
+        case EditActionTypingInsertFinalComposition:
         case EditActionPaste:
         case EditActionDrag:
         case EditActionSetWritingDirection:
diff --git a/Source/WebCore/editing/CompositeEditCommand.h b/Source/WebCore/editing/CompositeEditCommand.h
index 3149437..39bfa81 100644
--- a/Source/WebCore/editing/CompositeEditCommand.h
+++ b/Source/WebCore/editing/CompositeEditCommand.h
@@ -116,6 +116,7 @@
     virtual bool shouldStopCaretBlinking() const { return false; }
     virtual String inputEventTypeName() const;
     virtual String inputEventData() const { return { }; }
+    virtual bool isBeforeInputEventCancelable() const { return true; }
     Vector<RefPtr<StaticRange>> targetRangesForBindings() const;
 
 protected:
diff --git a/Source/WebCore/editing/EditAction.h b/Source/WebCore/editing/EditAction.h
index ba3cc54..9d99dad7 100644
--- a/Source/WebCore/editing/EditAction.h
+++ b/Source/WebCore/editing/EditAction.h
@@ -71,9 +71,13 @@
         EditActionTypingDeleteWordForward,
         EditActionTypingDeleteLineBackward,
         EditActionTypingDeleteLineForward,
+        EditActionTypingDeletePendingComposition,
+        EditActionTypingDeleteFinalComposition,
         EditActionTypingInsertText,
         EditActionTypingInsertLineBreak,
         EditActionTypingInsertParagraph,
+        EditActionTypingInsertPendingComposition,
+        EditActionTypingInsertFinalComposition,
         EditActionCreateLink,
         EditActionUnlink,
         EditActionFormatBlock,
diff --git a/Source/WebCore/editing/EditCommand.cpp b/Source/WebCore/editing/EditCommand.cpp
index 4da4e33..158a027 100644
--- a/Source/WebCore/editing/EditCommand.cpp
+++ b/Source/WebCore/editing/EditCommand.cpp
@@ -83,6 +83,10 @@
         return ASCIILiteral("deleteHardLineBackward");
     case EditActionTypingDeleteLineForward:
         return ASCIILiteral("deleteHardLineForward");
+    case EditActionTypingDeletePendingComposition:
+        return ASCIILiteral("deleteCompositionText");
+    case EditActionTypingDeleteFinalComposition:
+        return ASCIILiteral("deleteByComposition");
     case EditActionInsert:
     case EditActionTypingInsertText:
         return ASCIILiteral("insertText");
@@ -96,6 +100,10 @@
         return ASCIILiteral("insertOrderedList");
     case EditActionInsertUnorderedList:
         return ASCIILiteral("insertUnorderedList");
+    case EditActionTypingInsertPendingComposition:
+        return ASCIILiteral("insertCompositionText");
+    case EditActionTypingInsertFinalComposition:
+        return ASCIILiteral("insertFromComposition");
     case EditActionIndent:
         return ASCIILiteral("formatIndent");
     case EditActionOutdent:
diff --git a/Source/WebCore/editing/Editor.cpp b/Source/WebCore/editing/Editor.cpp
index 7ee880c..ea9d865 100644
--- a/Source/WebCore/editing/Editor.cpp
+++ b/Source/WebCore/editing/Editor.cpp
@@ -111,20 +111,20 @@
 
 namespace WebCore {
 
-static bool dispatchBeforeInputEvent(Element& element, const AtomicString& inputType, const String& data = { }, const Vector<RefPtr<StaticRange>>& targetRanges = { })
+static bool dispatchBeforeInputEvent(Element& element, const AtomicString& inputType, const String& data = { }, const Vector<RefPtr<StaticRange>>& targetRanges = { }, bool cancelable = true)
 {
     auto* settings = element.document().settings();
     if (!settings || !settings->inputEventsEnabled())
         return true;
 
-    return element.dispatchEvent(InputEvent::create(eventNames().beforeinputEvent, inputType, true, true, element.document().defaultView(), data, targetRanges, 0));
+    return element.dispatchEvent(InputEvent::create(eventNames().beforeinputEvent, inputType, true, cancelable, element.document().defaultView(), data, targetRanges, 0));
 }
 
 static void dispatchInputEvent(Element& element, const AtomicString& inputType, const String& data = { }, const Vector<RefPtr<StaticRange>>& targetRanges = { })
 {
     auto* settings = element.document().settings();
     if (settings && settings->inputEventsEnabled())
-        element.dispatchScopedEvent(InputEvent::create(eventNames().inputEvent, inputType, true, false, element.document().defaultView(), data, targetRanges, 0));
+        element.dispatchEvent(InputEvent::create(eventNames().inputEvent, inputType, true, false, element.document().defaultView(), data, targetRanges, 0));
     else
         element.dispatchInputEvent();
 }
@@ -1063,13 +1063,13 @@
         endingTextControl->didEditInnerTextValue();
 }
 
-static bool dispatchBeforeInputEvents(RefPtr<Element> startRoot, RefPtr<Element> endRoot, const AtomicString& inputTypeName, const String& data = { }, const Vector<RefPtr<StaticRange>>& targetRanges = { })
+static bool dispatchBeforeInputEvents(RefPtr<Element> startRoot, RefPtr<Element> endRoot, const AtomicString& inputTypeName, const String& data = { }, const Vector<RefPtr<StaticRange>>& targetRanges = { }, bool cancelable = true)
 {
     bool continueWithDefaultBehavior = true;
     if (startRoot)
-        continueWithDefaultBehavior &= dispatchBeforeInputEvent(*startRoot, inputTypeName, data, targetRanges);
+        continueWithDefaultBehavior &= dispatchBeforeInputEvent(*startRoot, inputTypeName, data, targetRanges, cancelable);
     if (endRoot && endRoot != startRoot)
-        continueWithDefaultBehavior &= dispatchBeforeInputEvent(*endRoot, inputTypeName, data, targetRanges);
+        continueWithDefaultBehavior &= dispatchBeforeInputEvent(*endRoot, inputTypeName, data, targetRanges, cancelable);
     return continueWithDefaultBehavior;
 }
 
@@ -1087,7 +1087,7 @@
     if (!composition)
         return true;
 
-    return dispatchBeforeInputEvents(composition->startingRootEditableElement(), composition->endingRootEditableElement(), command.inputEventTypeName(), command.inputEventData(), targetRanges);
+    return dispatchBeforeInputEvents(composition->startingRootEditableElement(), composition->endingRootEditableElement(), command.inputEventTypeName(), command.inputEventData(), targetRanges, command.isBeforeInputEventCancelable());
 }
 
 void Editor::appliedEditing(PassRefPtr<CompositeEditCommand> cmd)
@@ -1261,7 +1261,7 @@
                     options |= TypingCommand::SelectInsertedText;
                 if (autocorrectionWasApplied)
                     options |= TypingCommand::RetainAutocorrectionIndicator;
-                TypingCommand::insertText(document, text, selection, options, triggeringEvent && triggeringEvent->isComposition() ? TypingCommand::TextCompositionConfirm : TypingCommand::TextCompositionNone);
+                TypingCommand::insertText(document, text, selection, options, triggeringEvent && triggeringEvent->isComposition() ? TypingCommand::TextCompositionFinal : TypingCommand::TextCompositionNone);
             }
 
             // Reveal the current selection
@@ -1832,10 +1832,11 @@
         target->dispatchEvent(event);
     }
 
-    // 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)
-        TypingCommand::deleteSelection(document(), 0);
+    // Always delete the current composition before inserting the finalized composition text if we're confirming our composition.
+    // Our default behavior (if the beforeinput event is not prevented) is to insert the finalized composition text back in.
+    // We pass TypingCommand::TextCompositionPending here to indicate that we are deleting the pending composition.
+    if (mode != CancelComposition)
+        TypingCommand::deleteSelection(document(), 0, TypingCommand::TextCompositionPending);
 
     m_compositionNode = nullptr;
     m_customCompositionUnderlines.clear();
@@ -1868,6 +1869,18 @@
         return;
     }
 
+    String originalText = selectedText();
+    bool isStartingToRecomposeExistingRange = !text.isEmpty() && selectionStart < selectionEnd && !hasComposition();
+    if (isStartingToRecomposeExistingRange) {
+        // We pass TypingCommand::TextCompositionFinal here to indicate that we are removing composition text that has been finalized.
+        TypingCommand::deleteSelection(document(), 0, TypingCommand::TextCompositionFinal);
+        const VisibleSelection& currentSelection = m_frame.selection().selection();
+        if (currentSelection.isRange()) {
+            // If deletion was prevented, then we need to collapse the selection to the end so that the original text will not be recomposed.
+            m_frame.selection().setSelection({ currentSelection.end(), currentSelection.end() });
+        }
+    }
+
 #if PLATFORM(IOS)
     client()->startDelayingAndCoalescingContentChangeNotifications();
 #endif
@@ -1894,7 +1907,7 @@
             // We should send a compositionstart event only when the given text is not empty because this
             // function doesn't create a composition node when the text is empty.
             if (!text.isEmpty()) {
-                target->dispatchEvent(CompositionEvent::create(eventNames().compositionstartEvent, document().domWindow(), selectedText()));
+                target->dispatchEvent(CompositionEvent::create(eventNames().compositionstartEvent, document().domWindow(), originalText));
                 event = CompositionEvent::create(eventNames().compositionupdateEvent, document().domWindow(), text);
             }
         } else {
@@ -1910,13 +1923,13 @@
     // 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())
-        TypingCommand::deleteSelection(document(), TypingCommand::PreventSpellChecking);
+        TypingCommand::deleteSelection(document(), TypingCommand::PreventSpellChecking, TypingCommand::TextCompositionPending);
 
     m_compositionNode = nullptr;
     m_customCompositionUnderlines.clear();
 
     if (!text.isEmpty()) {
-        TypingCommand::insertText(document(), text, TypingCommand::SelectInsertedText | TypingCommand::PreventSpellChecking, TypingCommand::TextCompositionUpdate);
+        TypingCommand::insertText(document(), text, TypingCommand::SelectInsertedText | TypingCommand::PreventSpellChecking, TypingCommand::TextCompositionPending);
 
         // Find out what node has the composition now.
         Position base = m_frame.selection().selection().base().downstream();
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;
diff --git a/Source/WebCore/editing/TypingCommand.h b/Source/WebCore/editing/TypingCommand.h
index 810aa49..5083540 100644
--- a/Source/WebCore/editing/TypingCommand.h
+++ b/Source/WebCore/editing/TypingCommand.h
@@ -44,8 +44,8 @@
 
     enum TextCompositionType {
         TextCompositionNone,
-        TextCompositionUpdate,
-        TextCompositionConfirm
+        TextCompositionPending,
+        TextCompositionFinal,
     };
 
     enum Option {
@@ -57,7 +57,7 @@
     };
     typedef unsigned Options;
 
-    static void deleteSelection(Document&, Options = 0);
+    static void deleteSelection(Document&, Options = 0, TextCompositionType = TextCompositionNone);
     static void deleteKeyPressed(Document&, Options = 0, TextGranularity = CharacterGranularity);
     static void forwardDeleteKeyPressed(Document&, Options = 0, TextGranularity = CharacterGranularity);
     static void insertText(Document&, const String&, Options, TextCompositionType = TextCompositionNone);
@@ -85,9 +85,9 @@
 #endif
 
 private:
-    static Ref<TypingCommand> create(Document& document, ETypingCommand command, const String& text = emptyString(), Options options = 0, TextGranularity granularity = CharacterGranularity)
+    static Ref<TypingCommand> create(Document& document, ETypingCommand command, const String& text = emptyString(), Options options = 0, TextGranularity granularity = CharacterGranularity, TextCompositionType compositionType = TextCompositionNone)
     {
-        return adoptRef(*new TypingCommand(document, command, text, options, granularity, TextCompositionNone));
+        return adoptRef(*new TypingCommand(document, command, text, options, granularity, compositionType));
     }
 
     static Ref<TypingCommand> create(Document& document, ETypingCommand command, const String& text, Options options, TextCompositionType compositionType)
@@ -118,6 +118,7 @@
 
     String inputEventTypeName() const final;
     String inputEventData() const final;
+    bool isBeforeInputEventCancelable() const final;
 
     static void updateSelectionIfDifferentFromCurrentSelection(TypingCommand*, Frame*);