From def92d1b8e9d373e2f6f27c366d578d97d8960c6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 15 May 2024 05:34:50 +0200 Subject: Merging upstream version 126.0. Signed-off-by: Daniel Baumann --- editor/libeditor/EditAction.h | 25 +++--- editor/libeditor/EditorBase.cpp | 118 ++++++++++++++++++++++------- editor/libeditor/EditorBase.h | 2 + editor/libeditor/HTMLEditUtils.cpp | 2 +- editor/libeditor/HTMLEditor.cpp | 12 ++- editor/libeditor/tests/test_bug569988.html | 11 +-- 6 files changed, 114 insertions(+), 56 deletions(-) (limited to 'editor') diff --git a/editor/libeditor/EditAction.h b/editor/libeditor/EditAction.h index 6b900b0587..f74d1c6949 100644 --- a/editor/libeditor/EditAction.h +++ b/editor/libeditor/EditAction.h @@ -81,16 +81,19 @@ enum class EditAction { // new non-empty composition string and IME selections. eUpdateComposition, - // eCommitComposition indicates that user commits composition. + // eUpdateCompositionToCommit indicates that user commits composition with + // the new data. That means that there will be no IME selections, but the + // composition continues until the following eCompositionEnd event. + eUpdateCompositionToCommit, + + // eCommitComposition indicates that user commits composition and ends the + // composition. eCommitComposition, - // eCancelComposition indicates that user cancels composition. + // eCancelComposition indicates that user cancels composition and ends the + // composition with empty string. eCancelComposition, - // eDeleteByComposition indicates that user starts composition with - // empty string and there was selected content. - eDeleteByComposition, - // eUndo/eRedo indicate to undo/redo a transaction. eUndo, eRedo, @@ -547,6 +550,7 @@ inline EditorInputType ToInputType(EditAction aEditAction) { case EditAction::ePasteAsQuotation: return EditorInputType::eInsertFromPasteAsQuotation; case EditAction::eUpdateComposition: + case EditAction::eUpdateCompositionToCommit: return EditorInputType::eInsertCompositionText; case EditAction::eCommitComposition: if (StaticPrefs::dom_input_events_conform_to_level_1()) { @@ -558,13 +562,6 @@ inline EditorInputType ToInputType(EditAction aEditAction) { return EditorInputType::eInsertCompositionText; } return EditorInputType::eDeleteCompositionText; - case EditAction::eDeleteByComposition: - if (StaticPrefs::dom_input_events_conform_to_level_1()) { - // XXX Or EditorInputType::eDeleteContent? I don't know which IME may - // causes this situation. - return EditorInputType::eInsertCompositionText; - } - return EditorInputType::eDeleteByComposition; case EditAction::eInsertLinkElement: return EditorInputType::eInsertLink; case EditAction::eDeleteWordBackward: @@ -713,9 +710,9 @@ inline bool MayEditActionDeleteSelection(const EditAction aEditAction) { return false; case EditAction::eUpdateComposition: + case EditAction::eUpdateCompositionToCommit: case EditAction::eCommitComposition: case EditAction::eCancelComposition: - case EditAction::eDeleteByComposition: return true; case EditAction::eUndo: diff --git a/editor/libeditor/EditorBase.cpp b/editor/libeditor/EditorBase.cpp index e8120d93d9..cd1439e4c0 100644 --- a/editor/libeditor/EditorBase.cpp +++ b/editor/libeditor/EditorBase.cpp @@ -34,6 +34,7 @@ #include "ErrorList.h" #include "gfxFontUtils.h" // for gfxFontUtils #include "mozilla/Assertions.h" +#include "mozilla/AsyncEventDispatcher.h" #include "mozilla/intl/BidiEmbeddingLevel.h" #include "mozilla/BasePrincipal.h" // for BasePrincipal #include "mozilla/CheckedInt.h" // for CheckedInt @@ -3714,32 +3715,20 @@ nsresult EditorBase::OnCompositionChange( return NS_ERROR_FAILURE; } - AutoEditActionDataSetter editActionData(*this, - EditAction::eUpdateComposition); + AutoEditActionDataSetter editActionData( + *this, + // We need to distinguish whether the composition change is followed by + // compositionend or not (i.e., wether IME has already ended the + // composition or still has the composition) because we need to dispatch + // `textInput` event only for the last composition change. + aCompositionChangeEvent.IsFollowedByCompositionEnd() + ? EditAction::eUpdateCompositionToCommit + : EditAction::eUpdateComposition); if (NS_WARN_IF(!editActionData.CanHandle())) { return NS_ERROR_NOT_INITIALIZED; } - - // If: - // - new composition string is not empty, - // - there is no composition string in the DOM tree, - // - and there is non-collapsed Selection, - // the selected content will be removed by this composition. - if (aCompositionChangeEvent.mData.IsEmpty() && - mComposition->String().IsEmpty() && !SelectionRef().IsCollapsed()) { - editActionData.UpdateEditAction(EditAction::eDeleteByComposition); - } - - // If Input Events Level 2 is enabled, EditAction::eDeleteByComposition is - // mapped to EditorInputType::eDeleteByComposition and it requires null - // for InputEvent.data. Therefore, only otherwise, we should set data. - if (ToInputType(editActionData.GetEditAction()) != - EditorInputType::eDeleteByComposition) { - MOZ_ASSERT(ToInputType(editActionData.GetEditAction()) == - EditorInputType::eInsertCompositionText); - MOZ_ASSERT(!aCompositionChangeEvent.mData.IsVoid()); - editActionData.SetData(aCompositionChangeEvent.mData); - } + MOZ_ASSERT(!aCompositionChangeEvent.mData.IsVoid()); + editActionData.SetData(aCompositionChangeEvent.mData); // If we're an `HTMLEditor` and this is second or later composition change, // we should set target range to the range of composition string. @@ -6513,6 +6502,13 @@ nsresult EditorBase::AutoEditActionDataSetter::MaybeFlushPendingNotifications() return NS_OK; } +void EditorBase::AutoEditActionDataSetter::MarkEditActionCanceled() { + mBeforeInputEventCanceled = true; + if (mEditorBase.IsHTMLEditor()) { + mEditorBase.AsHTMLEditor()->mHasBeforeInputBeenCanceled = true; + } +} + nsresult EditorBase::AutoEditActionDataSetter::MaybeDispatchBeforeInputEvent( nsIEditor::EDirection aDeleteDirectionAndAmount /* = nsIEditor::eNone */) { MOZ_ASSERT(!HasTriedToDispatchBeforeInputEvent(), @@ -6637,11 +6633,79 @@ nsresult EditorBase::AutoEditActionDataSetter::MaybeDispatchBeforeInputEvent( NS_WARNING("nsContentUtils::DispatchInputEvent() failed"); return rv; } - mBeforeInputEventCanceled = status == nsEventStatus_eConsumeNoDefault; - if (mBeforeInputEventCanceled && mEditorBase.IsHTMLEditor()) { - mEditorBase.AsHTMLEditor()->mHasBeforeInputBeenCanceled = true; + if (status == nsEventStatus_eConsumeNoDefault) { + MarkEditActionCanceled(); + return NS_ERROR_EDITOR_ACTION_CANCELED; + } + + nsCOMPtr widget = editorBase->GetWidget(); + if (!StaticPrefs::dom_events_textevent_enabled() || + !targetElement->IsInComposedDoc() || !widget) { + return NS_OK; + } + nsString textInputData; + RefPtr textInputDataTransfer; + switch (inputType) { + case EditorInputType::eInsertCompositionText: + // If the composition is still being composed, we should not dispatch + // textInput event, but we need to dispatch it for the last composition + // change because web apps should know the inserting commit string as + // same as input from keyboard. + if (mEditAction == EditAction::eUpdateComposition) { + return NS_OK; + } + [[fallthrough]]; + case EditorInputType::eInsertText: + textInputData = mData; + break; + case EditorInputType::eInsertFromDrop: + case EditorInputType::eInsertFromPaste: + case EditorInputType::eInsertFromPasteAsQuotation: + if (mDataTransfer) { + textInputDataTransfer = mDataTransfer; + } else { + textInputData = mData; + } + break; + case EditorInputType::eInsertLineBreak: + case EditorInputType::eInsertParagraph: + // Don't dispatch `textInput` on because Chrome does not do it. + // On the other hand, we need to dispatch it on