diff options
Diffstat (limited to 'editor')
-rw-r--r-- | editor/libeditor/EditAction.h | 25 | ||||
-rw-r--r-- | editor/libeditor/EditorBase.cpp | 118 | ||||
-rw-r--r-- | editor/libeditor/EditorBase.h | 2 | ||||
-rw-r--r-- | editor/libeditor/HTMLEditUtils.cpp | 2 | ||||
-rw-r--r-- | editor/libeditor/HTMLEditor.cpp | 12 | ||||
-rw-r--r-- | editor/libeditor/tests/test_bug569988.html | 11 |
6 files changed, 114 insertions, 56 deletions
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<nsIWidget> widget = editorBase->GetWidget(); + if (!StaticPrefs::dom_events_textevent_enabled() || + !targetElement->IsInComposedDoc() || !widget) { + return NS_OK; + } + nsString textInputData; + RefPtr<DataTransfer> 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 <input> because Chrome does not do it. + // On the other hand, we need to dispatch it on <textarea> and + // contenteditable. + if (mEditorBase.IsTextEditor() && mEditorBase.IsSingleLineEditor()) { + return NS_OK; + } + textInputData.Assign(u'\n'); + break; + default: + return NS_OK; + } + + InternalLegacyTextEvent textEvent(true, eLegacyTextInput, widget); + textEvent.mData = std::move(textInputData); + textEvent.mDataTransfer = std::move(textInputDataTransfer); + textEvent.mInputType = inputType; + // Make it always cancelable even though we ignore it when inserting or + // deleting composition. This is compatible with Chrome. + // However, if and only if it's unsafe, let's set it not cancelable because of + // asynchronous dispatching. + textEvent.mFlags.mCancelable = nsContentUtils::IsSafeToRunScript(); + + status = nsEventStatus_eIgnore; + rv = AsyncEventDispatcher::RunDOMEventWhenSafe(*targetElement, textEvent, + &status); + if (NS_WARN_IF(mEditorBase.Destroyed())) { + return NS_ERROR_EDITOR_DESTROYED; } - return mBeforeInputEventCanceled ? NS_ERROR_EDITOR_ACTION_CANCELED : NS_OK; + if (NS_FAILED(rv)) { + NS_WARNING("AsyncEventDispatcher::RunDOMEventWhenSafe() failed"); + return rv; + } + if (status == nsEventStatus_eConsumeNoDefault) { + MarkEditActionCanceled(); + return NS_ERROR_EDITOR_ACTION_CANCELED; + } + return NS_OK; } /***************************************************************************** diff --git a/editor/libeditor/EditorBase.h b/editor/libeditor/EditorBase.h index 83f53ff215..f0335ac5d5 100644 --- a/editor/libeditor/EditorBase.h +++ b/editor/libeditor/EditorBase.h @@ -1361,6 +1361,8 @@ class EditorBase : public nsIEditor, } } + void MarkEditActionCanceled(); + EditorBase& mEditorBase; RefPtr<Selection> mSelection; nsTArray<OwningNonNull<Selection>> mRetiredSelections; diff --git a/editor/libeditor/HTMLEditUtils.cpp b/editor/libeditor/HTMLEditUtils.cpp index 8c3d09c1e2..c2cbbe6157 100644 --- a/editor/libeditor/HTMLEditUtils.cpp +++ b/editor/libeditor/HTMLEditUtils.cpp @@ -538,7 +538,7 @@ bool HTMLEditUtils::IsLink(const nsINode* aNode) { return false; } - nsAutoString tmpText; + nsAutoCString tmpText; anchor->GetHref(tmpText); return !tmpText.IsEmpty(); } diff --git a/editor/libeditor/HTMLEditor.cpp b/editor/libeditor/HTMLEditor.cpp index e9ea8887b7..fc88c79477 100644 --- a/editor/libeditor/HTMLEditor.cpp +++ b/editor/libeditor/HTMLEditor.cpp @@ -3773,10 +3773,14 @@ nsresult HTMLEditor::InsertLinkAroundSelectionAsAction( return EditorBase::ToGenericNSResult(rv); } - nsAutoString href; - anchor->GetHref(href); - if (href.IsEmpty()) { - return NS_OK; + // XXX Is this ok? Does this just want to check that we're a link? If so + // there are faster ways to do this. + { + nsAutoCString href; + anchor->GetHref(href); + if (href.IsEmpty()) { + return NS_OK; + } } AutoPlaceholderBatch treatAsOneTransaction( diff --git a/editor/libeditor/tests/test_bug569988.html b/editor/libeditor/tests/test_bug569988.html index 6181668374..2ff84d9d05 100644 --- a/editor/libeditor/tests/test_bug569988.html +++ b/editor/libeditor/tests/test_bug569988.html @@ -31,17 +31,9 @@ function runTest() { var os = Services.obs; os.addObserver(onPromptLoad, "common-dialog-loaded"); - os.addObserver(onPromptLoad, "tabmodal-dialog-loaded"); function onPromptLoad(subject) { - let ui = subject.Dialog ? subject.Dialog.ui : undefined; - if (!ui) { - // subject is an tab prompt, find the elements ourselves - ui = { - loginTextbox: subject.querySelector(".tabmodalprompt-loginTextbox"), - button0: subject.querySelector(".tabmodalprompt-button0"), - }; - } + let ui = subject.Dialog.ui; sendAsyncMessage("ok", [true, "onPromptLoad is called"]); gPromptInput = ui.loginTextbox; gPromptInput.addEventListener("focus", onPromptFocus); @@ -77,7 +69,6 @@ function runTest() { } addMessageListener("destroy", function() { - os.removeObserver(onPromptLoad, "tabmodal-dialog-loaded"); os.removeObserver(onPromptLoad, "common-dialog-loaded"); }); }); |