Bug 1213589 part.2 Clean up GenerateFlatTextContent(), GelerateFlatFontRanges() and GetFlatTextOffsetOfRange() of ContentEventHandler r=smaug
authorMasayuki Nakano <masayuki@d-toybox.com>
Wed, 02 Dec 2015 13:20:00 +0900
changeset 275161 d045e3b436223420edaf2933fb281d4f6cc464a3
parent 275160 cd5fdb842f905c8afccf35efa4dbb95d6981c675
child 275162 95efe6bad896355799aaaacea515f003c0c14f7e
push id29747
push usercbook@mozilla.com
push dateWed, 02 Dec 2015 14:21:19 +0000
treeherdermozilla-central@f6ac392322b3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1213589
milestone45.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1213589 part.2 Clean up GenerateFlatTextContent(), GelerateFlatFontRanges() and GetFlatTextOffsetOfRange() of ContentEventHandler r=smaug
dom/events/ContentEventHandler.cpp
dom/events/ContentEventHandler.h
dom/events/IMEContentObserver.cpp
dom/events/IMEContentObserver.h
--- a/dom/events/ContentEventHandler.cpp
+++ b/dom/events/ContentEventHandler.cpp
@@ -402,47 +402,51 @@ static uint32_t ConvertToXPOffset(nsICon
   // offset minus the number of newlines encountered in the string.
   return aNativeOffset - CountNewlinesInNativeLength(aContent, aNativeOffset);
 #else
   // On other platforms, the native and XP newlines are the same.
   return aNativeOffset;
 #endif
 }
 
-static nsresult GenerateFlatTextContent(nsRange* aRange,
-                                        nsAFlatString& aString,
-                                        LineBreakType aLineBreakType)
+nsresult
+ContentEventHandler::GenerateFlatTextContent(nsRange* aRange,
+                                             nsAFlatString& aString,
+                                             LineBreakType aLineBreakType)
 {
-  nsCOMPtr<nsIContentIterator> iter = NS_NewContentIterator();
-  iter->Init(aRange);
-
   NS_ASSERTION(aString.IsEmpty(), "aString must be empty string");
 
   nsINode* startNode = aRange->GetStartParent();
-  NS_ENSURE_TRUE(startNode, NS_ERROR_FAILURE);
   nsINode* endNode = aRange->GetEndParent();
-  NS_ENSURE_TRUE(endNode, NS_ERROR_FAILURE);
+  if (NS_WARN_IF(!startNode) || NS_WARN_IF(!endNode)) {
+    return NS_ERROR_FAILURE;
+  }
 
   if (startNode == endNode && startNode->IsNodeOfType(nsINode::eTEXT)) {
-    nsIContent* content = static_cast<nsIContent*>(startNode);
+    nsIContent* content = startNode->AsContent();
     AppendSubString(aString, content, aRange->StartOffset(),
                     aRange->EndOffset() - aRange->StartOffset());
     ConvertToNativeNewlines(aString);
     return NS_OK;
   }
 
+  nsCOMPtr<nsIContentIterator> iter = NS_NewContentIterator();
+  nsresult rv = iter->Init(aRange);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
   for (; !iter->IsDone(); iter->Next()) {
     nsINode* node = iter->GetCurrentNode();
-    if (!node) {
+    if (NS_WARN_IF(!node)) {
       break;
     }
-    if (!node->IsNodeOfType(nsINode::eCONTENT)) {
+    if (!node->IsContent()) {
       continue;
     }
-    nsIContent* content = static_cast<nsIContent*>(node);
+    nsIContent* content = node->AsContent();
 
     if (content->IsNodeOfType(nsINode::eTEXT)) {
       if (content == startNode) {
         AppendSubString(aString, content, aRange->StartOffset(),
                         content->TextLength() - aRange->StartOffset());
       } else if (content == endNode) {
         AppendSubString(aString, content, 0, aRange->EndOffset());
       } else {
@@ -573,24 +577,28 @@ ContentEventHandler::GenerateFlatFontRan
                                             FontRangeArray& aFontRanges,
                                             uint32_t& aLength,
                                             LineBreakType aLineBreakType)
 {
   MOZ_ASSERT(aFontRanges.IsEmpty(), "aRanges must be empty array");
 
   nsINode* startNode = aRange->GetStartParent();
   nsINode* endNode = aRange->GetEndParent();
-  if (NS_WARN_IF(!startNode || !endNode)) {
+  if (NS_WARN_IF(!startNode) || NS_WARN_IF(!endNode)) {
     return NS_ERROR_FAILURE;
   }
 
   // baseOffset is the flattened offset of each content node.
   int32_t baseOffset = 0;
   nsCOMPtr<nsIContentIterator> iter = NS_NewContentIterator();
-  for (iter->Init(aRange); !iter->IsDone(); iter->Next()) {
+  nsresult rv = iter->Init(aRange);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+  for (; !iter->IsDone(); iter->Next()) {
     nsINode* node = iter->GetCurrentNode();
     if (NS_WARN_IF(!node)) {
       break;
     }
     if (!node->IsContent()) {
       continue;
     }
     nsIContent* content = node->AsContent();
@@ -685,30 +693,32 @@ ContentEventHandler::SetRangeFromFlatTex
                                                 uint32_t* aNewOffset)
 {
   if (aNewOffset) {
     *aNewOffset = aOffset;
   }
 
   nsCOMPtr<nsIContentIterator> iter = NS_NewPreContentIterator();
   nsresult rv = iter->Init(mRootContent);
-  NS_ENSURE_SUCCESS(rv, rv);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
 
   uint32_t offset = 0;
   uint32_t endOffset = aOffset + aLength;
   bool startSet = false;
   for (; !iter->IsDone(); iter->Next()) {
     nsINode* node = iter->GetCurrentNode();
-    if (!node) {
+    if (NS_WARN_IF(!node)) {
       break;
     }
-    if (!node->IsNodeOfType(nsINode::eCONTENT)) {
+    if (!node->IsContent()) {
       continue;
     }
-    nsIContent* content = static_cast<nsIContent*>(node);
+    nsIContent* content = node->AsContent();
 
     uint32_t textLength =
       content->IsNodeOfType(nsINode::eTEXT) ?
         GetTextLength(content, aLineBreakType) :
         (IsContentBR(content) ? GetBRLength(aLineBreakType) : 0);
     if (!textLength) {
       continue;
     }
@@ -717,84 +727,98 @@ ContentEventHandler::SetRangeFromFlatTex
       uint32_t xpOffset;
       if (!content->IsNodeOfType(nsINode::eTEXT)) {
         xpOffset = 0;
       } else {
         xpOffset = aOffset - offset;
         if (aLineBreakType == LINE_BREAK_TYPE_NATIVE) {
           xpOffset = ConvertToXPOffset(content, xpOffset);
         }
-      }
 
-      if (aExpandToClusterBoundaries) {
-        uint32_t oldXPOffset = xpOffset;
-        rv = ExpandToClusterBoundary(content, false, &xpOffset);
-        NS_ENSURE_SUCCESS(rv, rv);
-        if (aNewOffset) {
-          // This is correct since a cluster shouldn't include line break.
-          *aNewOffset -= (oldXPOffset - xpOffset);
+        if (aExpandToClusterBoundaries) {
+          uint32_t oldXPOffset = xpOffset;
+          rv = ExpandToClusterBoundary(content, false, &xpOffset);
+          if (NS_WARN_IF(NS_FAILED(rv))) {
+            return rv;
+          }
+          if (aNewOffset) {
+            // This is correct since a cluster shouldn't include line break.
+            *aNewOffset -= (oldXPOffset - xpOffset);
+          }
         }
       }
 
       rv = aRange->SetStart(content, int32_t(xpOffset));
-      NS_ENSURE_SUCCESS(rv, rv);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
       startSet = true;
       if (aLength == 0) {
         // Ensure that the end offset and the start offset are same.
         rv = aRange->SetEnd(content, int32_t(xpOffset));
-        NS_ENSURE_SUCCESS(rv, rv);
+        if (NS_WARN_IF(NS_FAILED(rv))) {
+          return rv;
+        }
         return NS_OK;
       }
     }
     if (endOffset <= offset + textLength) {
       nsINode* endNode = content;
       uint32_t xpOffset;
       if (content->IsNodeOfType(nsINode::eTEXT)) {
         xpOffset = endOffset - offset;
         if (aLineBreakType == LINE_BREAK_TYPE_NATIVE) {
           xpOffset = ConvertToXPOffset(content, xpOffset);
         }
         if (aExpandToClusterBoundaries) {
           rv = ExpandToClusterBoundary(content, true, &xpOffset);
-          NS_ENSURE_SUCCESS(rv, rv);
+          if (NS_WARN_IF(NS_FAILED(rv))) {
+            return rv;
+          }
         }
       } else {
         // Use first position of next node, because the end node is ignored
         // by ContentIterator when the offset is zero.
         xpOffset = 0;
         iter->Next();
         if (iter->IsDone()) {
           break;
         }
         endNode = iter->GetCurrentNode();
       }
 
       rv = aRange->SetEnd(endNode, int32_t(xpOffset));
-      NS_ENSURE_SUCCESS(rv, rv);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
       return NS_OK;
     }
 
     offset += textLength;
   }
 
   if (offset < aOffset) {
     return NS_ERROR_FAILURE;
   }
 
   if (!startSet) {
     MOZ_ASSERT(!mRootContent->IsNodeOfType(nsINode::eTEXT));
     rv = aRange->SetStart(mRootContent, int32_t(mRootContent->GetChildCount()));
-    NS_ENSURE_SUCCESS(rv, rv);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
     if (aNewOffset) {
       *aNewOffset = offset;
     }
   }
   rv = aRange->SetEnd(mRootContent, int32_t(mRootContent->GetChildCount()));
-  NS_ASSERTION(NS_SUCCEEDED(rv), "nsRange::SetEnd failed");
-  return rv;
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+  return NS_OK;
 }
 
 /* static */ LineBreakType
 ContentEventHandler::GetLineBreakType(WidgetQueryContentEvent* aEvent)
 {
   return GetLineBreakType(aEvent->mUseNativeLineBreak);
 }
 
@@ -863,18 +887,18 @@ ContentEventHandler::OnQuerySelectedText
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   NS_ASSERTION(aEvent->mReply.mString.IsEmpty(),
                "The reply string must be empty");
 
   LineBreakType lineBreakType = GetLineBreakType(aEvent);
-  rv = GetFlatTextOffsetOfRange(mRootContent, mFirstSelectedRange,
-                                &aEvent->mReply.mOffset, lineBreakType);
+  rv = GetFlatTextLengthBefore(mFirstSelectedRange,
+                               &aEvent->mReply.mOffset, lineBreakType);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsINode> anchorNode, focusNode;
   int32_t anchorOffset, focusOffset;
   if (mSelection->RangeCount()) {
     anchorNode = mSelection->GetAnchorNode();
     focusNode = mSelection->GetFocusNode();
     if (NS_WARN_IF(!anchorNode) || NS_WARN_IF(!focusNode)) {
@@ -1135,18 +1159,18 @@ ContentEventHandler::OnQueryCaretRect(Wi
   nsRect caretRect;
 
   // When the selection is collapsed and the queried offset is current caret
   // position, we should return the "real" caret rect.
   if (mSelection->IsCollapsed()) {
     nsIFrame* caretFrame = nsCaret::GetGeometry(mSelection, &caretRect);
     if (caretFrame) {
       uint32_t offset;
-      rv = GetFlatTextOffsetOfRange(mRootContent, mFirstSelectedRange, &offset,
-                                    lineBreakType);
+      rv = GetFlatTextLengthBefore(mFirstSelectedRange,
+                                   &offset, lineBreakType);
       NS_ENSURE_SUCCESS(rv, rv);
       if (offset == aEvent->mInput.mOffset) {
         rv = ConvertToRootRelativeOffset(caretFrame, caretRect);
         NS_ENSURE_SUCCESS(rv, rv);
         nscoord appUnitsPerDevPixel =
           caretFrame->PresContext()->AppUnitsPerDevPixel();
         aEvent->mReply.mRect = LayoutDeviceIntRect::FromUnknownRect(
           caretRect.ToOutsidePixels(appUnitsPerDevPixel));
@@ -1315,18 +1339,19 @@ ContentEventHandler::OnQueryCharacterAtP
   if (!tentativeCaretOffsets.content ||
       !nsContentUtils::ContentIsDescendantOf(tentativeCaretOffsets.content,
                                              mRootContent)) {
     // There is no character nor tentative caret point at the point.
     aEvent->mSucceeded = true;
     return NS_OK;
   }
 
-  rv = GetFlatTextOffsetOfRange(mRootContent, tentativeCaretOffsets.content,
-                                tentativeCaretOffsets.offset,
+  rv = GetFlatTextLengthInRange(NodePosition(mRootContent, 0),
+                                NodePosition(tentativeCaretOffsets),
+                                mRootContent,
                                 &aEvent->mReply.mTentativeCaretOffset,
                                 GetLineBreakType(aEvent));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   if (targetFrame->GetType() != nsGkAtoms::textFrame) {
     // There is no character at the point but there is tentative caret point.
@@ -1339,20 +1364,23 @@ ContentEventHandler::OnQueryCharacterAtP
     "The point is inside a character bounding box.  Why tentative caret point "
     "hasn't been found?");
 
   nsTextFrame* textframe = static_cast<nsTextFrame*>(targetFrame);
   nsIFrame::ContentOffsets contentOffsets =
     textframe->GetCharacterOffsetAtFramePoint(ptInTarget);
   NS_ENSURE_TRUE(contentOffsets.content, NS_ERROR_FAILURE);
   uint32_t offset;
-  rv = GetFlatTextOffsetOfRange(mRootContent, contentOffsets.content,
-                                contentOffsets.offset, &offset,
+  rv = GetFlatTextLengthInRange(NodePosition(mRootContent, 0),
+                                NodePosition(contentOffsets),
+                                mRootContent, &offset,
                                 GetLineBreakType(aEvent));
-  NS_ENSURE_SUCCESS(rv, rv);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
 
   WidgetQueryContentEvent textRect(true, eQueryTextRect, aEvent->widget);
   textRect.InitForQueryTextRect(offset, 1, aEvent->mUseNativeLineBreak);
   rv = OnQueryTextRect(&textRect);
   NS_ENSURE_SUCCESS(rv, rv);
   NS_ENSURE_TRUE(textRect.mSucceeded, NS_ERROR_FAILURE);
 
   // currently, we don't need to get the actual text.
@@ -1404,86 +1432,99 @@ ContentEventHandler::OnQueryDOMWidgetHit
     }
   }
 
   aEvent->mSucceeded = true;
   return NS_OK;
 }
 
 /* static */ nsresult
-ContentEventHandler::GetFlatTextOffsetOfRange(nsIContent* aRootContent,
-                                              nsINode* aNode,
-                                              int32_t aNodeOffset,
-                                              uint32_t* aOffset,
-                                              LineBreakType aLineBreakType)
+ContentEventHandler::GetFlatTextLengthInRange(
+                       const NodePosition& aStartPosition,
+                       const NodePosition& aEndPosition,
+                       nsIContent* aRootContent,
+                       uint32_t* aLength,
+                       LineBreakType aLineBreakType)
 {
-  NS_ENSURE_STATE(aRootContent);
-  NS_ASSERTION(aOffset, "param is invalid");
+  if (NS_WARN_IF(!aRootContent) || NS_WARN_IF(!aStartPosition.IsValid()) ||
+      NS_WARN_IF(!aEndPosition.IsValid()) || NS_WARN_IF(!aLength)) {
+    return NS_ERROR_INVALID_ARG;
+  }
 
   RefPtr<nsRange> prev = new nsRange(aRootContent);
-  nsCOMPtr<nsIDOMNode> rootDOMNode(do_QueryInterface(aRootContent));
-  prev->SetStart(rootDOMNode, 0);
-
-  nsCOMPtr<nsIDOMNode> startDOMNode(do_QueryInterface(aNode));
-  NS_ASSERTION(startDOMNode, "startNode doesn't have nsIDOMNode");
+  nsresult rv = aStartPosition.SetToRangeStart(prev);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
 
   nsCOMPtr<nsIContentIterator> iter = NS_NewContentIterator();
-
-  if (aNode->Length() >= static_cast<uint32_t>(aNodeOffset)) {
+  if (aEndPosition.OffsetIsValid()) {
     // Offset is within node's length; set end of range to that offset
-    prev->SetEnd(startDOMNode, aNodeOffset);
-    iter->Init(prev);
-  } else if (aNode != static_cast<nsINode*>(aRootContent)) {
+    rv = aEndPosition.SetToRangeEnd(prev);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+    rv = iter->Init(prev);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+  } else if (aEndPosition.mNode != aRootContent) {
     // Offset is past node's length; set end of range to end of node
-    prev->SetEndAfter(startDOMNode);
-    iter->Init(prev);
+    rv = aEndPosition.SetToRangeEndAfter(prev);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+    rv = iter->Init(prev);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
   } else {
     // Offset is past the root node; set end of range to end of root node
-    iter->Init(aRootContent);
+    rv = iter->Init(aRootContent);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
   }
 
-  nsCOMPtr<nsINode> startNode = do_QueryInterface(startDOMNode);
-  nsINode* endNode = aNode;
-
-  *aOffset = 0;
+  *aLength = 0;
   for (; !iter->IsDone(); iter->Next()) {
     nsINode* node = iter->GetCurrentNode();
-    if (!node) {
+    if (NS_WARN_IF(!node)) {
       break;
     }
-    if (!node->IsNodeOfType(nsINode::eCONTENT)) {
+    if (!node->IsContent()) {
       continue;
     }
-    nsIContent* content = static_cast<nsIContent*>(node);
+    nsIContent* content = node->AsContent();
 
     if (node->IsNodeOfType(nsINode::eTEXT)) {
       // Note: our range always starts from offset 0
-      if (node == endNode) {
-        *aOffset += GetTextLength(content, aLineBreakType, aNodeOffset);
+      if (node == aEndPosition.mNode) {
+        *aLength += GetTextLength(content, aLineBreakType,
+                                  aEndPosition.mOffset);
       } else {
-        *aOffset += GetTextLength(content, aLineBreakType);
+        *aLength += GetTextLength(content, aLineBreakType);
       }
     } else if (IsContentBR(content)) {
-      *aOffset += GetBRLength(aLineBreakType);
+      *aLength += GetBRLength(aLineBreakType);
     }
   }
   return NS_OK;
 }
 
-/* static */ nsresult
-ContentEventHandler::GetFlatTextOffsetOfRange(nsIContent* aRootContent,
-                                              nsRange* aRange,
-                                              uint32_t* aOffset,
-                                              LineBreakType aLineBreakType)
+nsresult
+ContentEventHandler::GetFlatTextLengthBefore(nsRange* aRange,
+                                             uint32_t* aOffset,
+                                             LineBreakType aLineBreakType)
 {
-  nsINode* startNode = aRange->GetStartParent();
-  NS_ENSURE_TRUE(startNode, NS_ERROR_FAILURE);
-  int32_t startOffset = aRange->StartOffset();
-  return GetFlatTextOffsetOfRange(aRootContent, startNode, startOffset,
-                                  aOffset, aLineBreakType);
+  MOZ_ASSERT(aRange);
+  return GetFlatTextLengthInRange(
+           NodePosition(mRootContent, 0),
+           NodePosition(aRange->GetStartParent(), aRange->StartOffset()),
+           mRootContent, aOffset, aLineBreakType);
 }
 
 nsresult
 ContentEventHandler::AdjustCollapsedRangeMaybeIntoTextNode(nsRange* aRange)
 {
   MOZ_ASSERT(aRange);
   MOZ_ASSERT(aRange->Collapsed());
 
--- a/dom/events/ContentEventHandler.h
+++ b/dom/events/ContentEventHandler.h
@@ -5,16 +5,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_ContentEventHandler_h_
 #define mozilla_ContentEventHandler_h_
 
 #include "mozilla/EventForwards.h"
 #include "mozilla/dom/Selection.h"
 #include "nsCOMPtr.h"
+#include "nsIFrame.h"
+#include "nsINode.h"
 #include "nsRange.h"
 
 class nsPresContext;
 
 struct nsRect;
 
 namespace mozilla {
 
@@ -76,25 +78,70 @@ protected:
 
   nsresult InitBasic();
   nsresult InitCommon();
 
 public:
   // FlatText means the text that is generated from DOM tree. The BR elements
   // are replaced to native linefeeds. Other elements are ignored.
 
-  // Get the offset in FlatText of the range. (also used by IMEContentObserver)
-  static nsresult GetFlatTextOffsetOfRange(nsIContent* aRootContent,
-                                           nsINode* aNode,
-                                           int32_t aNodeOffset,
-                                           uint32_t* aOffset,
-                                           LineBreakType aLineBreakType);
-  static nsresult GetFlatTextOffsetOfRange(nsIContent* aRootContent,
-                                           nsRange* aRange,
-                                           uint32_t* aOffset,
+  // NodePosition stores a pair of node and offset in the node.
+  // This is useful to receive one or more sets of them instead of nsRange.
+  struct NodePosition final
+  {
+    nsCOMPtr<nsINode> mNode;
+    int32_t mOffset;
+
+    NodePosition()
+      : mOffset(-1)
+    {
+    }
+
+    NodePosition(nsINode* aNode, int32_t aOffset)
+      : mNode(aNode)
+      , mOffset(aOffset)
+    {
+    }
+
+    explicit NodePosition(const nsIFrame::ContentOffsets& aContentOffsets)
+      : mNode(aContentOffsets.content)
+      , mOffset(aContentOffsets.offset)
+    {
+    }
+
+    bool IsValid() const
+    {
+      return mNode && mOffset >= 0;
+    }
+    bool OffsetIsValid() const
+    {
+      return IsValid() && static_cast<uint32_t>(mOffset) <= mNode->Length();
+    }
+    nsresult SetToRangeStart(nsRange* aRange) const
+    {
+      nsCOMPtr<nsIDOMNode> domNode(do_QueryInterface(mNode));
+      return aRange->SetStart(domNode, mOffset);
+    }
+    nsresult SetToRangeEnd(nsRange* aRange) const
+    {
+      nsCOMPtr<nsIDOMNode> domNode(do_QueryInterface(mNode));
+      return aRange->SetEnd(domNode, mOffset);
+    }
+    nsresult SetToRangeEndAfter(nsRange* aRange) const
+    {
+      nsCOMPtr<nsIDOMNode> domNode(do_QueryInterface(mNode));
+      return aRange->SetEndAfter(domNode);
+    }
+  };
+
+  // Get the flatten text length in the range.
+  static nsresult GetFlatTextLengthInRange(const NodePosition& aStartPosition,
+                                           const NodePosition& aEndPosition,
+                                           nsIContent* aRootContent,
+                                           uint32_t* aLength,
                                            LineBreakType aLineBreakType);
   // Computes the native text length between aStartOffset and aEndOffset of
   // aContent.  aContent must be a text node.
   static uint32_t GetNativeTextLength(nsIContent* aContent,
                                       uint32_t aStartOffset,
                                       uint32_t aEndOffset);
   // Get the native text length of aContent.  aContent must be a text node.
   static uint32_t GetNativeTextLength(nsIContent* aContent,
@@ -109,16 +156,24 @@ protected:
                                 LineBreakType aLineBreakType,
                                 uint32_t aMaxLength = UINT32_MAX);
   // Get the text length of a given range of a content node in
   // the given line break type.
   static uint32_t GetTextLengthInRange(nsIContent* aContent,
                                        uint32_t aXPStartOffset,
                                        uint32_t aXPEndOffset,
                                        LineBreakType aLineBreakType);
+  // Get the contents of aRange as plain text.
+  nsresult GenerateFlatTextContent(nsRange* aRange,
+                                   nsAFlatString& aString,
+                                   LineBreakType aLineBreakType);
+  // Get the text length before the start position of aRange.
+  nsresult GetFlatTextLengthBefore(nsRange* aRange,
+                                   uint32_t* aOffset,
+                                   LineBreakType aLineBreakType);
   // Get the line breaker length.
   static inline uint32_t GetBRLength(LineBreakType aLineBreakType);
   static LineBreakType GetLineBreakType(WidgetQueryContentEvent* aEvent);
   static LineBreakType GetLineBreakType(WidgetSelectionEvent* aEvent);
   static LineBreakType GetLineBreakType(bool aUseNativeLineBreak);
   // Returns focused content (including its descendant documents).
   nsIContent* GetFocusedContent();
   // Returns true if the content is a plugin host.
--- a/dom/events/IMEContentObserver.cpp
+++ b/dom/events/IMEContentObserver.cpp
@@ -871,21 +871,25 @@ IMEContentObserver::CharacterDataChanged
 
   MOZ_ASSERT(removedLength >= 0,
              "mPreCharacterDataChangeLength should've been set by "
              "CharacterDataWillChange()");
 
   uint32_t offset = 0;
   // get offsets of change and fire notification
   nsresult rv =
-    ContentEventHandler::GetFlatTextOffsetOfRange(mRootContent, aContent,
-                                                  aInfo->mChangeStart,
-                                                  &offset,
-                                                  LINE_BREAK_TYPE_NATIVE);
-  NS_ENSURE_SUCCESS_VOID(rv);
+    ContentEventHandler::GetFlatTextLengthInRange(
+                           NodePosition(mRootContent, 0),
+                           NodePosition(aContent, aInfo->mChangeStart),
+                           mRootContent,
+                           &offset,
+                           LINE_BREAK_TYPE_NATIVE);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return;
+  }
 
   uint32_t newLength =
     ContentEventHandler::GetNativeTextLength(aContent, aInfo->mChangeStart,
                                              aInfo->mChangeStart +
                                                aInfo->mReplaceLength);
 
   uint32_t oldEnd = offset + static_cast<uint32_t>(removedLength);
   uint32_t newEnd = offset + newLength;
@@ -907,32 +911,35 @@ IMEContentObserver::NotifyContentAdded(n
       !mUpdatePreference.WantChangesCausedByComposition()) {
     return;
   }
 
   uint32_t offset = 0;
   nsresult rv = NS_OK;
   if (!mEndOfAddedTextCache.Match(aContainer, aStartIndex)) {
     mEndOfAddedTextCache.Clear();
-    rv = ContentEventHandler::GetFlatTextOffsetOfRange(mRootContent, aContainer,
-                                                       aStartIndex, &offset,
-                                                       LINE_BREAK_TYPE_NATIVE);
+    rv = ContentEventHandler::GetFlatTextLengthInRange(
+                                NodePosition(mRootContent, 0),
+                                NodePosition(aContainer, aStartIndex),
+                                mRootContent, &offset,
+                                LINE_BREAK_TYPE_NATIVE);
     if (NS_WARN_IF(NS_FAILED((rv)))) {
       return;
     }
   } else {
     offset = mEndOfAddedTextCache.mFlatTextLength;
   }
 
   // get offset at the end of the last added node
-  nsIContent* childAtStart = aContainer->GetChildAt(aStartIndex);
   uint32_t addingLength = 0;
-  rv = ContentEventHandler::GetFlatTextOffsetOfRange(childAtStart, aContainer,
-                                                     aEndIndex, &addingLength,
-                                                     LINE_BREAK_TYPE_NATIVE);
+  rv = ContentEventHandler::GetFlatTextLengthInRange(
+                              NodePosition(aContainer, aStartIndex),
+                              NodePosition(aContainer, aEndIndex),
+                              mRootContent, &addingLength,
+                              LINE_BREAK_TYPE_NATIVE);
   if (NS_WARN_IF(NS_FAILED((rv)))) {
     mEndOfAddedTextCache.Clear();
     return;
   }
 
   // If multiple lines are being inserted in an HTML editor, next call of
   // NotifyContentAdded() is for adding next node.  Therefore, caching the text
   // length can skip to compute the text length before the adding node and
@@ -983,43 +990,48 @@ IMEContentObserver::ContentRemoved(nsIDo
     return;
   }
 
   nsINode* containerNode = NODE_FROM(aContainer, aDocument);
 
   uint32_t offset = 0;
   nsresult rv = NS_OK;
   if (!mStartOfRemovingTextRangeCache.Match(containerNode, aIndexInContainer)) {
-    rv =
-      ContentEventHandler::GetFlatTextOffsetOfRange(mRootContent, containerNode,
-                                                    aIndexInContainer, &offset,
-                                                    LINE_BREAK_TYPE_NATIVE);
+    rv = ContentEventHandler::GetFlatTextLengthInRange(
+                                NodePosition(mRootContent, 0),
+                                NodePosition(containerNode, aIndexInContainer),
+                                mRootContent,
+                                &offset,
+                                LINE_BREAK_TYPE_NATIVE);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       mStartOfRemovingTextRangeCache.Clear();
       return;
     }
     mStartOfRemovingTextRangeCache.Cache(containerNode, aIndexInContainer,
                                          offset);
   } else {
     offset = mStartOfRemovingTextRangeCache.mFlatTextLength;
   }
 
   // get offset at the end of the deleted node
-  int32_t nodeLength =
-    aChild->IsNodeOfType(nsINode::eTEXT) ?
-      static_cast<int32_t>(aChild->TextLength()) :
+  uint32_t textLength = 0;
+  if (aChild->IsNodeOfType(nsINode::eTEXT)) {
+    textLength = ContentEventHandler::GetNativeTextLength(aChild);
+  } else {
+    uint32_t nodeLength =
       std::max(static_cast<int32_t>(aChild->GetChildCount()), 1);
-  MOZ_ASSERT(nodeLength >= 0, "The node length is out of range");
-  uint32_t textLength = 0;
-  rv = ContentEventHandler::GetFlatTextOffsetOfRange(aChild, aChild,
-                                                     nodeLength, &textLength,
-                                                     LINE_BREAK_TYPE_NATIVE);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    mStartOfRemovingTextRangeCache.Clear();
-    return;
+    rv = ContentEventHandler::GetFlatTextLengthInRange(
+                                NodePosition(aChild, 0),
+                                NodePosition(aChild, nodeLength),
+                                mRootContent, &textLength,
+                                LINE_BREAK_TYPE_NATIVE);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      mStartOfRemovingTextRangeCache.Clear();
+      return;
+    }
   }
 
   if (!textLength) {
     return;
   }
 
   TextChangeData data(offset, offset + textLength, offset,
                       causedByComposition, IsEditorComposing());
@@ -1057,20 +1069,24 @@ IMEContentObserver::AttributeChanged(nsI
 
   uint32_t postAttrChangeLength =
     ContentEventHandler::GetNativeTextLengthBefore(aElement);
   if (postAttrChangeLength == mPreAttrChangeLength) {
     return;
   }
   uint32_t start;
   nsresult rv =
-    ContentEventHandler::GetFlatTextOffsetOfRange(mRootContent, aElement,
-                                                  0, &start,
+    ContentEventHandler::GetFlatTextLengthInRange(NodePosition(mRootContent, 0),
+                                                  NodePosition(aElement, 0),
+                                                  mRootContent,
+                                                  &start,
                                                   LINE_BREAK_TYPE_NATIVE);
-  NS_ENSURE_SUCCESS_VOID(rv);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return;
+  }
 
   TextChangeData data(start, start + mPreAttrChangeLength,
                       start + postAttrChangeLength, causedByComposition,
                       IsEditorComposing());
   MaybeNotifyIMEOfTextChange(data);
 }
 
 void
--- a/dom/events/IMEContentObserver.h
+++ b/dom/events/IMEContentObserver.h
@@ -35,16 +35,17 @@ class EventStateManager;
 class IMEContentObserver final : public nsISelectionListener
                                , public nsStubMutationObserver
                                , public nsIReflowObserver
                                , public nsIScrollObserver
                                , public nsSupportsWeakReference
                                , public nsIEditorObserver
 {
 public:
+  typedef ContentEventHandler::NodePosition NodePosition;
   typedef widget::IMENotification::SelectionChangeData SelectionChangeData;
   typedef widget::IMENotification::TextChangeData TextChangeData;
   typedef widget::IMENotification::TextChangeDataBase TextChangeDataBase;
   typedef widget::IMEMessage IMEMessage;
 
   IMEContentObserver();
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS