diff --git a/dom/events/IMEContentObserver.cpp b/dom/events/IMEContentObserver.cpp index eb0c901d035f..f21348f0c4a8 100644 --- a/dom/events/IMEContentObserver.cpp +++ b/dom/events/IMEContentObserver.cpp @@ -792,6 +792,15 @@ void IMEContentObserver::CharacterDataChanged( // node. } + // Let TextComposition have a change to update composition string range in + // the text node if the change is caused by the web apps. + if (mWidget && !IsEditorHandlingEventForComposition()) { + if (RefPtr composition = + IMEStateManager::GetTextCompositionFor(mWidget)) { + composition->OnCharacterDataChanged(*aContent->AsText(), aInfo); + } + } + if (!NeedsTextChangeNotification() || !nsContentUtils::IsInSameAnonymousTree(mRootContent, aContent)) { return; diff --git a/dom/events/TextComposition.cpp b/dom/events/TextComposition.cpp index 8bcfcd1c3344..676da92d2174 100644 --- a/dom/events/TextComposition.cpp +++ b/dom/events/TextComposition.cpp @@ -9,6 +9,7 @@ #include "IMEStateManager.h" #include "nsContentUtils.h" #include "nsIContent.h" +#include "nsIMutationObserver.h" #include "nsPresContext.h" #include "mozilla/AutoRestore.h" #include "mozilla/EditorBase.h" @@ -87,6 +88,82 @@ void TextComposition::Destroy() { // this being destroyed for cleaning up the stuff. } +void TextComposition::OnCharacterDataChanged( + Text& aText, const CharacterDataChangeInfo& aInfo) { + if (mContainerTextNode != &aText || + mCompositionStartOffsetInTextNode == UINT32_MAX || + mCompositionLengthInTextNode == UINT32_MAX) { + return; + } + + // Ignore changes after composition string. + if (aInfo.mChangeStart >= + mCompositionStartOffsetInTextNode + mCompositionLengthInTextNode) { + return; + } + + // If the change ends before the composition string, we need only to adjust + // the start offset. + if (aInfo.mChangeEnd <= mCompositionStartOffsetInTextNode) { + MOZ_ASSERT(aInfo.LengthOfRemovedText() <= + mCompositionStartOffsetInTextNode); + mCompositionStartOffsetInTextNode -= aInfo.LengthOfRemovedText(); + mCompositionStartOffsetInTextNode += aInfo.mReplaceLength; + return; + } + + // If this is caused by a splitting text node, the composition string + // may be split out to the new right node. In the case, + // CompositionTransaction::DoTransaction handles it with warking the + // following text nodes. Therefore, we should NOT shrink the composing + // range for avoind breaking the fix of bug 1310912. Although the handling + // looks buggy so that we need to move the handling into here later. + if (aInfo.mDetails && + aInfo.mDetails->mType == CharacterDataChangeInfo::Details::eSplit) { + return; + } + + // If the change removes/replaces the last character of the composition + // string, we should shrink the composition range before the change start. + // Then, the replace string will be never updated by coming composition + // updates. + if (aInfo.mChangeEnd >= + mCompositionStartOffsetInTextNode + mCompositionLengthInTextNode) { + // If deleting the first character of the composition string, collapse IME + // selection temporarily. Updating composition string will insert new + // composition string there. + if (aInfo.mChangeStart <= mCompositionStartOffsetInTextNode) { + mCompositionStartOffsetInTextNode = aInfo.mChangeStart; + mCompositionLengthInTextNode = 0u; + return; + } + // If some characters in the composition still stay, composition range + // should be shrunken. + MOZ_ASSERT(aInfo.mChangeStart > mCompositionStartOffsetInTextNode); + mCompositionLengthInTextNode = + aInfo.mChangeStart - mCompositionStartOffsetInTextNode; + return; + } + + // If removed range starts in the composition string, we need only adjust + // the length to make composition range contain the replace string. + if (aInfo.mChangeStart >= mCompositionStartOffsetInTextNode) { + MOZ_ASSERT(aInfo.LengthOfRemovedText() <= mCompositionLengthInTextNode); + mCompositionLengthInTextNode -= aInfo.LengthOfRemovedText(); + mCompositionLengthInTextNode += aInfo.mReplaceLength; + return; + } + + // If preceding characers of the composition string is also removed, new + // composition start will be there and new composition ends at current + // position. + const uint32_t removedLengthInCompositionString = + aInfo.mChangeEnd - mCompositionStartOffsetInTextNode; + mCompositionStartOffsetInTextNode = aInfo.mChangeStart; + mCompositionLengthInTextNode -= removedLengthInCompositionString; + mCompositionLengthInTextNode += aInfo.mReplaceLength; +} + bool TextComposition::IsValidStateForComposition(nsIWidget* aWidget) const { return !Destroyed() && aWidget && !aWidget->Destroyed() && mPresContext->GetPresShell() && diff --git a/dom/events/TextComposition.h b/dom/events/TextComposition.h index 613a166b0204..86e5c5843277 100644 --- a/dom/events/TextComposition.h +++ b/dom/events/TextComposition.h @@ -21,6 +21,10 @@ #include "mozilla/dom/BrowserParent.h" #include "mozilla/dom/Text.h" +class nsRange; + +struct CharacterDataChangeInfo; + namespace mozilla { class EditorBase; @@ -274,6 +278,13 @@ class TextComposition final { // composition in new text node. } + /** + * OnCharacterDataChanged() is called when IMEContentObserver receives + * character data change notifications. + */ + void OnCharacterDataChanged(Text& aText, + const CharacterDataChangeInfo& aInfo); + private: // Private destructor, to discourage deletion outside of Release(): ~TextComposition() { diff --git a/editor/libeditor/CompositionTransaction.cpp b/editor/libeditor/CompositionTransaction.cpp index f0bde85c919e..6d97a8ac794a 100644 --- a/editor/libeditor/CompositionTransaction.cpp +++ b/editor/libeditor/CompositionTransaction.cpp @@ -125,6 +125,13 @@ NS_IMETHODIMP CompositionTransaction::DoTransaction() { // If composition string is split to multiple text nodes, we should put // whole new composition string to the first text node and remove the // compostion string in other nodes. + // TODO: This should be handled by `TextComposition` because this assumes + // that composition string has never touched by JS. However, it + // would occur if the web app is a corrabolation software which + // multiple users can modify anyware in an editor. + // TODO: And if composition starts from a following text node, the offset + // here is outdated and it will cause inserting composition string + // **before** the proper point from point of view of the users. uint32_t replaceableLength = textNode->TextLength() - mOffset; ErrorResult error; editorBase->DoReplaceText(textNode, mOffset, mReplaceLength, diff --git a/editor/libeditor/tests/test_bug1310912.html b/editor/libeditor/tests/test_bug1310912.html index 1581a1bec9f0..6c2036e4c081 100644 --- a/editor/libeditor/tests/test_bug1310912.html +++ b/editor/libeditor/tests/test_bug1310912.html @@ -22,96 +22,217 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1310912