Bug 1425390 - part 1: Move some IME related members of EditorBase into TextComposition r?m_kato draft
authorMasayuki Nakano <masayuki@d-toybox.com>
Fri, 15 Dec 2017 13:14:51 +0900
changeset 711986 33c7848261614ce04d02b3d3caee003d22926f68
parent 711985 c473611fde2b97d061c1305b4f7947268577066a
child 711987 861a5247399fc6f7af5ebd6a43442b9c3d14f7ec
push id93215
push usermasayuki@d-toybox.com
push dateFri, 15 Dec 2017 09:29:54 +0000
reviewersm_kato
bugs1425390
milestone59.0a1
Bug 1425390 - part 1: Move some IME related members of EditorBase into TextComposition r?m_kato EditorBase stores a text node, offset in it and length in it of composition string directly. However, this wastes memory space if user never uses IME or user only sometimes uses IME. Additionally, storing all data in TextComposition is better than current design when other classes like CompositionTransaction wants some information of both EditorBase and TextComposition. This patch moves those 3 members from EditorBase to TextComposition. MozReview-Commit-ID: 4N7wmGGfxmt
dom/events/TextComposition.cpp
dom/events/TextComposition.h
editor/libeditor/EditorBase.cpp
editor/libeditor/EditorBase.h
--- a/dom/events/TextComposition.cpp
+++ b/dom/events/TextComposition.cpp
@@ -54,16 +54,18 @@ TextComposition::TextComposition(nsPresC
                                  TabParent* aTabParent,
                                  WidgetCompositionEvent* aCompositionEvent)
   : mPresContext(aPresContext)
   , mNode(aNode)
   , mTabParent(aTabParent)
   , mNativeContext(aCompositionEvent->mNativeIMEContext)
   , mCompositionStartOffset(0)
   , mTargetClauseOffsetInComposition(0)
+  , mCompositionStartOffsetInTextNode(UINT32_MAX)
+  , mCompositionLengthInTextNode(UINT32_MAX)
   , mIsSynthesizedForTests(aCompositionEvent->mFlags.mIsSynthesizedForTests)
   , mIsComposing(false)
   , mIsEditorHandlingEvent(false)
   , mIsRequestingCommit(false)
   , mIsRequestingCancel(false)
   , mRequestedToCommitOrCancel(false)
   , mHasReceivedCommitEvent(false)
   , mWasNativeCompositionEndEventDiscarded(false)
@@ -76,16 +78,19 @@ TextComposition::TextComposition(nsPresC
 }
 
 void
 TextComposition::Destroy()
 {
   mPresContext = nullptr;
   mNode = nullptr;
   mTabParent = nullptr;
+  mContainerTextNode = nullptr;
+  mCompositionStartOffsetInTextNode = UINT32_MAX;
+  mCompositionLengthInTextNode = UINT32_MAX;
   // TODO: If the editor is still alive and this is held by it, we should tell
   //       this being destroyed for cleaning up the stuff.
 }
 
 bool
 TextComposition::IsValidStateForComposition(nsIWidget* aWidget) const
 {
   return !Destroyed() && aWidget && !aWidget->Destroyed() &&
--- a/dom/events/TextComposition.h
+++ b/dom/events/TextComposition.h
@@ -13,16 +13,17 @@
 #include "nsIWidget.h"
 #include "nsTArray.h"
 #include "nsThreadUtils.h"
 #include "nsPresContext.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/EventForwards.h"
 #include "mozilla/TextRange.h"
 #include "mozilla/dom/TabParent.h"
+#include "mozilla/dom/Text.h"
 
 namespace mozilla {
 
 class EditorBase;
 class EventDispatchingCallback;
 class IMEStateManager;
 
 /**
@@ -34,27 +35,30 @@ class IMEStateManager;
 class TextComposition final
 {
   friend class IMEStateManager;
 
   NS_INLINE_DECL_REFCOUNTING(TextComposition)
 
 public:
   typedef dom::TabParent TabParent;
+  typedef dom::Text Text;
 
   static bool IsHandlingSelectionEvent() { return sHandlingSelectionEvent; }
 
   TextComposition(nsPresContext* aPresContext,
                   nsINode* aNode,
                   TabParent* aTabParent,
                   WidgetCompositionEvent* aCompositionEvent);
 
   bool Destroyed() const { return !mPresContext; }
   nsPresContext* GetPresContext() const { return mPresContext; }
   nsINode* GetEventTargetNode() const { return mNode; }
+  // The text node which includes composition string.
+  Text* GetContainerTextNode() const { return mContainerTextNode; }
   // The latest CompositionEvent.data value except compositionstart event.
   // This value is modified at dispatching compositionupdate.
   const nsString& LastData() const { return mLastData; }
   // The composition string which is already handled by the focused editor.
   // I.e., this value must be same as the composition string on the focused
   // editor.  This value is modified at a call of
   // EditorDidHandleCompositionChangeEvent().
   // Note that mString and mLastData are different between dispatcing
@@ -127,31 +131,73 @@ public:
    * the offset of first selected clause or start of composition
    */
   uint32_t NativeOffsetOfTargetClause() const
   {
     return mCompositionStartOffset + mTargetClauseOffsetInComposition;
   }
 
   /**
+   * The offset of composition string in the text node.  If composition string
+   * hasn't been inserted in any text node yet, this returns UINT32_MAX.
+   */
+  uint32_t XPOffsetInTextNode() const
+  {
+    return mCompositionStartOffsetInTextNode;
+  }
+
+  /**
+   * The length of composition string in the text node.  If composition string
+   * hasn't been inserted in any text node yet, this returns UINT32_MAX.
+   */
+  uint32_t XPLengthInTextNode() const
+  {
+    return mCompositionLengthInTextNode;
+  }
+
+  /**
+   * The end offset of composition string in the text node.  If composition
+   * string hasn't been inserted in any text node yet, this returns UINT32_MAX.
+   */
+  uint32_t XPEndOffsetInTextNode() const
+  {
+    if (mCompositionStartOffsetInTextNode == UINT32_MAX ||
+        mCompositionLengthInTextNode == UINT32_MAX) {
+      return UINT32_MAX;
+    }
+    return mCompositionStartOffsetInTextNode + mCompositionLengthInTextNode;
+  }
+
+  /**
    * Returns true if there is non-empty composition string and it's not fixed.
    * Otherwise, false.
    */
   bool IsComposing() const { return mIsComposing; }
 
   /**
    * Returns true while editor is handling an event which is modifying the
    * composition string.
    */
   bool IsEditorHandlingEvent() const
   {
     return mIsEditorHandlingEvent;
   }
 
   /**
+   * IsMovingToNewTextNode() returns true if editor detects the text node
+   * has been removed and still not insert the composition string into
+   * new text node.
+   */
+  bool IsMovingToNewTextNode() const
+  {
+    return !mContainerTextNode && mCompositionLengthInTextNode &&
+           mCompositionLengthInTextNode != UINT32_MAX;
+  }
+
+  /**
    * StartHandlingComposition() and EndHandlingComposition() are called by
    * editor when it holds a TextComposition instance and release it.
    */
   void StartHandlingComposition(EditorBase* aEditorBase);
   void EndHandlingComposition(EditorBase* aEditorBase);
 
   /**
    * OnEditorDestroyed() is called when the editor is destroyed but there is
@@ -184,16 +230,76 @@ public:
 
   private:
     RefPtr<TextComposition> mComposition;
     CompositionChangeEventHandlingMarker();
     CompositionChangeEventHandlingMarker(
       const CompositionChangeEventHandlingMarker& aOther);
   };
 
+  /**
+   * WillCreateCompositionTransaction() is called by the focused editor
+   * immediately before creating CompositionTransaction.
+   *
+   * @param aTextNode       The text node which includes composition string.
+   * @param aOffset         The offset of composition string in aTextNode.
+   */
+  void WillCreateCompositionTransaction(Text* aTextNode,
+                                        uint32_t aOffset)
+  {
+    if (!mContainerTextNode) {
+      mContainerTextNode = aTextNode;
+      mCompositionStartOffsetInTextNode = aOffset;
+      NS_WARNING_ASSERTION(mCompositionStartOffsetInTextNode != UINT32_MAX,
+        "The text node is really too long.");
+    }
+#ifdef DEBUG
+    else {
+      NS_WARNING_ASSERTION(aTextNode == mContainerTextNode,
+        "The editor tries to insert composition string into different node");
+      NS_WARNING_ASSERTION(aOffset == mCompositionStartOffsetInTextNode,
+        "The editor tries to insert composition string into different offset");
+    }
+#endif // #ifdef DEBUG
+    if (mCompositionLengthInTextNode == UINT32_MAX) {
+      mCompositionLengthInTextNode = 0;
+    }
+  }
+
+  /**
+   * DidCreateCompositionTransaction() is called by the focused editor
+   * immediately after creating CompositionTransaction.
+   *
+   * @param aStringToInsert     The string to insert the text node actually.
+   *                            This may be different from the data of
+   *                            dispatching composition event because it may
+   *                            be replaced with different character for
+   *                            passwords, or truncated due to maxlength.
+   */
+  void DidCreateCompositionTransaction(const nsAString& aStringToInsert)
+  {
+    MOZ_ASSERT(mCompositionStartOffsetInTextNode != UINT32_MAX);
+    mCompositionLengthInTextNode = aStringToInsert.Length();
+    NS_WARNING_ASSERTION(mCompositionLengthInTextNode != UINT32_MAX,
+      "The string to insert is really too long.");
+  }
+
+  /**
+   * OnTextNodeRemoved() is called when focused editor is reframed and
+   * mContainerTextNode may be (or have been) replaced with different text
+   * node, or just removes the text node due to empty.
+   */
+  void OnTextNodeRemoved()
+  {
+    mContainerTextNode = nullptr;
+    // Don't reset mCompositionStartOffsetInTextNode nor
+    // mCompositionLengthInTextNode because editor needs them to restore
+    // composition in new text node.
+  }
+
 private:
   // Private destructor, to discourage deletion outside of Release():
   ~TextComposition()
   {
     // WARNING: mPresContext may be destroying, so, be careful if you touch it.
   }
 
   // sHandlingSelectionEvent is true while TextComposition sends a selection
@@ -203,16 +309,19 @@ private:
   // This class holds nsPresContext weak.  This instance shouldn't block
   // destroying it.  When the presContext is being destroyed, it's notified to
   // IMEStateManager::OnDestroyPresContext(), and then, it destroy
   // this instance.
   nsPresContext* mPresContext;
   nsCOMPtr<nsINode> mNode;
   RefPtr<TabParent> mTabParent;
 
+  // The text node which includes the composition string.
+  RefPtr<Text> mContainerTextNode;
+
   // This is the clause and caret range information which is managed by
   // the focused editor.  This may be null if there is no clauses or caret.
   RefPtr<TextRangeArray> mRanges;
   // Same as mRange, but mRange will have old data during compositionupdate.
   // So this will be valied during compositionupdate.
   RefPtr<TextRangeArray> mLastRanges;
 
   // mNativeContext stores a opaque pointer.  This works as the "ID" for this
@@ -231,16 +340,28 @@ private:
   // editor.
   nsString mString;
 
   // Offset of the composition string from start of the editor
   uint32_t mCompositionStartOffset;
   // Offset of the selected clause of the composition string from
   // mCompositionStartOffset
   uint32_t mTargetClauseOffsetInComposition;
+  // Offset of the composition string in mContainerTextNode.
+  // NOTE: This is NOT valid in the main process if focused editor is in a
+  //       remote process.
+  uint32_t mCompositionStartOffsetInTextNode;
+  // Length of the composition string in mContainerTextNode.  If this instance
+  // has already dispatched eCompositionCommit(AsIs) and
+  // EditorDidHandleCompositionChangeEvent() has already been called,
+  // this may be different from length of mString because committed string
+  // may be truncated by maxlength attribute of <input> or <textarea>.
+  // NOTE: This is NOT valid in the main process if focused editor is in a
+  //       remote process.
+  uint32_t mCompositionLengthInTextNode;
 
   // See the comment for IsSynthesizedForTests().
   bool mIsSynthesizedForTests;
 
   // See the comment for IsComposing().
   bool mIsComposing;
 
   // mIsEditorHandlingEvent is true while editor is modifying the composition
@@ -281,16 +402,18 @@ private:
   bool mWasCompositionStringEmpty;
 
   // Hide the default constructor and copy constructor.
   TextComposition()
     : mPresContext(nullptr)
     , mNativeContext(nullptr)
     , mCompositionStartOffset(0)
     , mTargetClauseOffsetInComposition(0)
+    , mCompositionStartOffsetInTextNode(UINT32_MAX)
+    , mCompositionLengthInTextNode(UINT32_MAX)
     , mIsSynthesizedForTests(false)
     , mIsComposing(false)
     , mIsEditorHandlingEvent(false)
     , mIsRequestingCommit(false)
     , mIsRequestingCancel(false)
     , mRequestedToCommitOrCancel(false)
     , mHasReceivedCommitEvent(false)
     , mWasNativeCompositionEndEventDiscarded(false)
--- a/editor/libeditor/EditorBase.cpp
+++ b/editor/libeditor/EditorBase.cpp
@@ -125,18 +125,16 @@ using namespace widget;
 
 EditorBase::EditorBase()
   : mPlaceholderName(nullptr)
   , mModCount(0)
   , mFlags(0)
   , mUpdateCount(0)
   , mPlaceholderBatch(0)
   , mAction(EditAction::none)
-  , mIMETextOffset(0)
-  , mIMETextLength(0)
   , mDirection(eNone)
   , mDocDirtyState(-1)
   , mSpellcheckCheckboxState(eTriUnset)
   , mShouldTxnSetSelection(true)
   , mDidPreDestroy(false)
   , mDidPostCreate(false)
   , mDispatchInputEvent(true)
   , mIsInEditAction(false)
@@ -163,17 +161,16 @@ EditorBase::~EditorBase()
 NS_IMPL_CYCLE_COLLECTION_CLASS(EditorBase)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(EditorBase)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mRootElement)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelectionController)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mInlineSpellChecker)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mTxnMgr)
- NS_IMPL_CYCLE_COLLECTION_UNLINK(mIMETextNode)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mActionListeners)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mEditorObservers)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocStateListeners)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mEventTarget)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mEventListener)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mPlaceholderTransaction)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mSavedSel);
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mRangeUpdater);
@@ -186,17 +183,16 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(
      nsCCUncollectableMarker::InGeneration(cb, currentDoc->GetMarkedCCGeneration())) {
    return NS_SUCCESS_INTERRUPTED_TRAVERSE;
  }
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRootElement)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelectionController)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInlineSpellChecker)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTxnMgr)
- NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIMETextNode)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mActionListeners)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEditorObservers)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocStateListeners)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEventTarget)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEventListener)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPlaceholderTransaction)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSavedSel);
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRangeUpdater);
@@ -252,23 +248,24 @@ EditorBase::Init(nsIDOMDocument* aDOMDoc
              "Selection controller should be available at this point");
 
   //set up root element if we are passed one.
   if (aRoot)
     mRootElement = do_QueryInterface(aRoot);
 
   mUpdateCount=0;
 
-  // If this is an editor for <input> or <textarea>, mIMETextNode is always
-  // recreated with same content. Therefore, we need to forget mIMETextNode,
-  // but we need to keep storing mIMETextOffset and mIMETextLength becuase
-  // they are necessary to restore IME selection and replacing composing string
-  // when this receives eCompositionChange event next time.
-  if (mIMETextNode && !mIMETextNode->IsInComposedDoc()) {
-    mIMETextNode = nullptr;
+  // If this is an editor for <input> or <textarea>, the text node which
+  // has composition string is always recreated with same content. Therefore,
+  // we need to nodify mComposition of text node destruction and replacing
+  // composing string when this receives eCompositionChange event next time.
+  if (mComposition &&
+      mComposition->GetContainerTextNode() &&
+      !mComposition->GetContainerTextNode()->IsInComposedDoc()) {
+    mComposition->OnTextNodeRemoved();
   }
 
   // Show the caret.
   selectionController->SetCaretReadOnly(false);
   selectionController->SetDisplaySelection(
                          nsISelectionController::SELECTION_ON);
   // Show all the selection reflected to user.
   selectionController->SetSelectionFlags(nsISelectionDisplay::DISPLAY_ALL);
@@ -2357,20 +2354,19 @@ EditorBase::EndIMEComposition()
                    "nsIAbsorbingTransaction::Commit() failed");
     }
   }
 
   // Composition string may have hidden the caret.  Therefore, we need to
   // cancel it here.
   HideCaret(false);
 
-  /* reset the data we need to construct a transaction */
-  mIMETextNode = nullptr;
-  mIMETextOffset = 0;
-  mIMETextLength = 0;
+  // FYI: mComposition still keeps storing container text node of committed
+  //      string, its offset and length.  However, they will be invalidated
+  //      soon since its Destroy() will be called by IMEStateManager.
   mComposition->EndHandlingComposition(this);
   mComposition = nullptr;
 
   // notify editor observers of action
   NotifyEditorObservers(eNotifyEditorObserversOfEnd);
 }
 
 NS_IMETHODIMP
@@ -2783,28 +2779,26 @@ EditorBase::InsertTextIntoTextNodeImpl(c
   RefPtr<EditTransactionBase> transaction;
   bool isIMETransaction = false;
   RefPtr<Text> insertedTextNode = &aTextNode;
   int32_t insertedOffset = aOffset;
   // aSuppressIME is used when editor must insert text, yet this text is not
   // part of the current IME operation. Example: adjusting whitespace around an
   // IME insertion.
   if (ShouldHandleIMEComposition() && !aSuppressIME) {
-    if (!mIMETextNode) {
-      mIMETextNode = &aTextNode;
-      mIMETextOffset = aOffset;
-    }
+    // XXX Here is still ugly.  This should be fixed by a follow up bug.
+    mComposition->WillCreateCompositionTransaction(&aTextNode, aOffset);
     transaction = CreateTxnForComposition(aStringToInsert);
+    mComposition->DidCreateCompositionTransaction(aStringToInsert);
     isIMETransaction = true;
     // All characters of the composition string will be replaced with
     // aStringToInsert.  So, we need to emulate to remove the composition
     // string.
-    insertedTextNode = mIMETextNode;
-    insertedOffset = mIMETextOffset;
-    mIMETextLength = aStringToInsert.Length();
+    insertedTextNode = mComposition->GetContainerTextNode();
+    insertedOffset = mComposition->XPOffsetInTextNode();
   } else {
     transaction = CreateTxnForInsertText(aStringToInsert, aTextNode, aOffset);
   }
 
   // Let listeners know what's up
   {
     AutoActionListenerArray listeners(mActionListeners);
     for (auto& listener : listeners) {
@@ -2835,21 +2829,21 @@ EditorBase::InsertTextIntoTextNodeImpl(c
   // nodes caused by IME.  I have to mark the IME transaction as "fixed", which
   // means that furure IME txns won't merge with it.  This is because we don't
   // want future IME txns trying to put their text into a node that is no
   // longer in the document.  This does not break undo/redo, because all these
   // txns are wrapped in a parent PlaceHolder txn, and placeholder txns are
   // already savvy to having multiple ime txns inside them.
 
   // Delete empty IME text node if there is one
-  if (isIMETransaction && mIMETextNode) {
-    uint32_t len = mIMETextNode->Length();
-    if (!len) {
-      DeleteNode(mIMETextNode);
-      mIMETextNode = nullptr;
+  if (isIMETransaction && mComposition) {
+    Text* textNode = mComposition->GetContainerTextNode();
+    if (textNode && !textNode->Length()) {
+      DeleteNode(textNode);
+      mComposition->OnTextNodeRemoved();
       static_cast<CompositionTransaction*>(transaction.get())->MarkFixed();
     }
   }
 
   return rv;
 }
 
 nsresult
@@ -4689,24 +4683,29 @@ EditorBase::CreateTxnForDeleteNode(nsINo
     return nullptr;
   }
   return deleteNodeTransaction.forget();
 }
 
 already_AddRefed<CompositionTransaction>
 EditorBase::CreateTxnForComposition(const nsAString& aStringToInsert)
 {
-  MOZ_ASSERT(mIMETextNode);
+  MOZ_ASSERT(mComposition);
+  MOZ_ASSERT(mComposition->GetContainerTextNode());
   // During handling IME composition, mComposition must have been initialized.
   // TODO: We can simplify CompositionTransaction::Init() with TextComposition
   //       class.
   RefPtr<CompositionTransaction> transaction =
-    new CompositionTransaction(*mIMETextNode, mIMETextOffset, mIMETextLength,
-                               mComposition->GetRanges(), aStringToInsert,
-                               *this, &mRangeUpdater);
+    new CompositionTransaction(*mComposition->GetContainerTextNode(),
+                               mComposition->XPOffsetInTextNode(),
+                               mComposition->XPLengthInTextNode(),
+                               mComposition->GetRanges(),
+                               aStringToInsert,
+                               *this,
+                               &mRangeUpdater);
   return transaction.forget();
 }
 
 already_AddRefed<AddStyleSheetTransaction>
 EditorBase::CreateTxnForAddStyleSheet(StyleSheet* aSheet)
 {
   RefPtr<AddStyleSheetTransaction> transaction =
     new AddStyleSheetTransaction(*this, aSheet);
@@ -5236,33 +5235,37 @@ EditorBase::InitializeSelection(nsIDOMEv
     if (!selection->RangeCount()) {
       BeginningOfDocument();
     }
   }
 
   // If there is composition when this is called, we may need to restore IME
   // selection because if the editor is reframed, this already forgot IME
   // selection and the transaction.
-  if (mComposition && !mIMETextNode && mIMETextLength) {
-    // We need to look for the new mIMETextNode from current selection.
+  if (mComposition && mComposition->IsMovingToNewTextNode()) {
+    // We need to look for the new text node from current selection.
     // XXX If selection is changed during reframe, this doesn't work well!
     nsRange* firstRange = selection->GetRangeAt(0);
-    NS_ENSURE_TRUE(firstRange, NS_ERROR_FAILURE);
+    if (NS_WARN_IF(!firstRange)) {
+      return NS_ERROR_FAILURE;
+    }
     EditorRawDOMPoint atStartOfFirstRange(firstRange->StartRef());
     EditorRawDOMPoint betterInsertionPoint =
       FindBetterInsertionPoint(atStartOfFirstRange);
     Text* textNode = betterInsertionPoint.GetContainerAsText();
     MOZ_ASSERT(textNode,
-               "There must be text node if mIMETextLength is larger than 0");
+               "There must be text node if composition string is not empty");
     if (textNode) {
-      MOZ_ASSERT(textNode->Length() >= mIMETextOffset + mIMETextLength,
-                 "The text node must be different from the old mIMETextNode");
-      CompositionTransaction::SetIMESelection(*this, textNode, mIMETextOffset,
-                                              mIMETextLength,
-                                              mComposition->GetRanges());
+      MOZ_ASSERT(textNode->Length() >= mComposition->XPEndOffsetInTextNode(),
+                 "The text node must be different from the old text node");
+      CompositionTransaction::SetIMESelection(
+                                *this, textNode,
+                                mComposition->XPOffsetInTextNode(),
+                                mComposition->XPLengthInTextNode(),
+                                mComposition->GetRanges());
     }
   }
 
   return NS_OK;
 }
 
 class RepaintSelectionRunner final : public Runnable {
 public:
--- a/editor/libeditor/EditorBase.h
+++ b/editor/libeditor/EditorBase.h
@@ -1548,18 +1548,16 @@ protected:
   // MIME type of the doc we are editing.
   nsCString mContentMIMEType;
 
   nsCOMPtr<nsIInlineSpellChecker> mInlineSpellChecker;
 
   RefPtr<nsTransactionManager> mTxnMgr;
   // Cached root node.
   nsCOMPtr<Element> mRootElement;
-  // Current IME text node.
-  RefPtr<Text> mIMETextNode;
   // The form field as an event receiver.
   nsCOMPtr<dom::EventTarget> mEventTarget;
   nsCOMPtr<nsIDOMEventListener> mEventListener;
   // Strong reference to placeholder for begin/end batch purposes.
   RefPtr<PlaceholderTransaction> mPlaceholderTransaction;
   // Name of placeholder transaction.
   nsAtom* mPlaceholderName;
   // Saved selection state for placeholder transaction batching.
@@ -1593,22 +1591,16 @@ protected:
 
   int32_t mUpdateCount;
 
   // Nesting count for batching.
   int32_t mPlaceholderBatch;
   // The current editor action.
   EditAction mAction;
 
-  // Offset in text node where IME comp string begins.
-  uint32_t mIMETextOffset;
-  // The Length of the composition string or commit string.  If this is length
-  // of commit string, the length is truncated by maxlength attribute.
-  uint32_t mIMETextLength;
-
   // The current direction of editor action.
   EDirection mDirection;
   // -1 = not initialized
   int8_t mDocDirtyState;
   // A Tristate value.
   uint8_t mSpellcheckCheckboxState;
 
   // Turn off for conservative selection adjustment by transactions.