Bug 1553378 - Devirtualize calls to GetText() / TextLength() when we know we have a Text node. r=smaug,jfkthame
authorCameron McCormack <cam@mcc.id.au>
Wed, 22 May 2019 15:18:48 +1000
changeset 475413 932a983ac0583c9c4311ec05b9779a0257bdb8ef
parent 475412 36ba94f116e5c033102b24d9c8b1736a94f7bdfa
child 475414 11edf789501b21a00794a5fc17e8584d9bf85807
push id36061
push usercbrindusan@mozilla.com
push dateFri, 24 May 2019 21:49:59 +0000
treeherdermozilla-central@5d3e1ea77693 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug, jfkthame
bugs1553378
milestone69.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 1553378 - Devirtualize calls to GetText() / TextLength() when we know we have a Text node. r=smaug,jfkthame Differential Revision: https://phabricator.services.mozilla.com/D32100
dom/base/CharacterData.cpp
dom/base/CharacterData.h
dom/base/DirectionalityUtils.cpp
dom/base/DirectionalityUtils.h
dom/base/Selection.cpp
dom/base/nsContentUtils.cpp
dom/base/nsRange.cpp
dom/events/ContentEventHandler.cpp
dom/svg/SVGTextContentElement.cpp
editor/libeditor/HTMLEditRules.cpp
editor/libeditor/TextEditor.cpp
editor/libeditor/WSRunObject.cpp
layout/base/RestyleManager.cpp
layout/base/nsBidiPresUtils.cpp
layout/base/nsCSSFrameConstructor.cpp
layout/base/nsCSSFrameConstructor.h
layout/base/nsLayoutUtils.cpp
layout/forms/nsTextControlFrame.cpp
layout/generic/nsFontInflationData.cpp
layout/generic/nsTextFrame.cpp
layout/generic/nsTextFrame.h
layout/generic/nsTextFrameUtils.cpp
layout/generic/nsTextFrameUtils.h
layout/svg/SVGTextFrame.cpp
--- a/dom/base/CharacterData.cpp
+++ b/dom/base/CharacterData.cpp
@@ -246,17 +246,18 @@ nsresult CharacterData::SetTextInternal(
     CharacterDataChangeInfo info = {aOffset == textLength, aOffset, endOffset,
                                     aLength, aDetails};
     nsNodeUtils::CharacterDataWillChange(this, info);
   }
 
   Directionality oldDir = eDir_NotSet;
   bool dirAffectsAncestor =
       (NodeType() == TEXT_NODE &&
-       TextNodeWillChangeDirection(this, &oldDir, aOffset));
+       TextNodeWillChangeDirection(static_cast<nsTextNode*>(this), &oldDir,
+                                   aOffset));
 
   if (aOffset == 0 && endOffset == textLength) {
     // Replacing whole text or old text was empty.  Don't bother to check for
     // bidi in this string if the document already has bidi enabled.
     // If this is marked as "maybe modified frequently", the text should be
     // stored as char16_t since converting char* to char16_t* is expensive.
     bool ok =
         mText.SetTo(aBuffer, aLength, !document || !document->GetBidiEnabled(),
--- a/dom/base/CharacterData.h
+++ b/dom/base/CharacterData.h
@@ -112,20 +112,20 @@ class CharacterData : public nsIContent 
 
   void UnbindFromTree(bool aDeep = true, bool aNullParent = true) override;
 
   already_AddRefed<nsINodeList> GetChildren(uint32_t aFilter) final {
     return nullptr;
   }
 
   const nsTextFragment* GetText() override { return &mText; }
+  uint32_t TextLength() const final { return TextDataLength(); }
 
   const nsTextFragment& TextFragment() const { return mText; }
-
-  uint32_t TextLength() const final { return TextDataLength(); }
+  uint32_t TextDataLength() const { return mText.GetLength(); }
 
   /**
    * Set the text to the given value. If aNotify is true then
    * the document is notified of the content change.
    */
   nsresult SetText(const char16_t* aBuffer, uint32_t aLength, bool aNotify);
   /**
    * Append the given value to the current text. If aNotify is true then
@@ -192,18 +192,16 @@ class CharacterData : public nsIContent 
   void SubstringData(uint32_t aStart, uint32_t aCount, nsAString& aReturn,
                      ErrorResult& rv);
   void AppendData(const nsAString& aData, ErrorResult& rv);
   void InsertData(uint32_t aOffset, const nsAString& aData, ErrorResult& rv);
   void DeleteData(uint32_t aOffset, uint32_t aCount, ErrorResult& rv);
   void ReplaceData(uint32_t aOffset, uint32_t aCount, const nsAString& aData,
                    ErrorResult& rv);
 
-  uint32_t TextDataLength() const { return mText.GetLength(); }
-
   //----------------------------------------
 
 #ifdef DEBUG
   void ToCString(nsAString& aBuf, int32_t aOffset, int32_t aLen) const;
 #endif
 
   NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS_INHERITED(
       CharacterData, nsIContent)
--- a/dom/base/DirectionalityUtils.cpp
+++ b/dom/base/DirectionalityUtils.cpp
@@ -373,24 +373,24 @@ static Directionality GetDirectionFromTe
   }
 
   if (aFirstStrong) {
     *aFirstStrong = UINT32_MAX;
   }
   return eDir_NotSet;
 }
 
-static Directionality GetDirectionFromText(const nsTextFragment* aFrag,
+static Directionality GetDirectionFromText(const Text* aTextNode,
                                            uint32_t* aFirstStrong = nullptr) {
-  if (aFrag->Is2b()) {
-    return GetDirectionFromText(aFrag->Get2b(), aFrag->GetLength(),
-                                aFirstStrong);
+  const nsTextFragment* frag = &aTextNode->TextFragment();
+  if (frag->Is2b()) {
+    return GetDirectionFromText(frag->Get2b(), frag->GetLength(), aFirstStrong);
   }
 
-  return GetDirectionFromText(aFrag->Get1b(), aFrag->GetLength(), aFirstStrong);
+  return GetDirectionFromText(frag->Get1b(), frag->GetLength(), aFirstStrong);
 }
 
 static nsTextNode* WalkDescendantsAndGetDirectionFromText(
     nsINode* aRoot, nsINode* aSkip, Directionality* aDirectionality) {
   nsIContent* child = aRoot->GetFirstChild();
   while (child) {
     if ((child->IsElement() &&
          DoesNotAffectDirectionOfAncestors(child->AsElement())) ||
@@ -400,41 +400,42 @@ static nsTextNode* WalkDescendantsAndGet
     }
 
     HTMLSlotElement* slot = HTMLSlotElement::FromNode(child);
     if (slot) {
       const nsTArray<RefPtr<nsINode>>& assignedNodes = slot->AssignedNodes();
       for (uint32_t i = 0; i < assignedNodes.Length(); ++i) {
         nsIContent* assignedNode = assignedNodes[i]->AsContent();
         if (assignedNode->NodeType() == nsINode::TEXT_NODE) {
+          auto text = static_cast<nsTextNode*>(assignedNode);
           if (assignedNode != aSkip) {
-            Directionality textNodeDir =
-                GetDirectionFromText(assignedNode->GetText());
+            Directionality textNodeDir = GetDirectionFromText(text);
             if (textNodeDir != eDir_NotSet) {
               *aDirectionality = textNodeDir;
-              return static_cast<nsTextNode*>(assignedNode);
+              return text;
             }
           }
         } else if (assignedNode->IsElement() &&
                    !DoesNotAffectDirectionOfAncestors(
                        assignedNode->AsElement())) {
           nsTextNode* text = WalkDescendantsAndGetDirectionFromText(
               assignedNode, aSkip, aDirectionality);
           if (text) {
             return text;
           }
         }
       }
     }
 
     if (child->NodeType() == nsINode::TEXT_NODE && child != aSkip) {
-      Directionality textNodeDir = GetDirectionFromText(child->GetText());
+      auto text = static_cast<nsTextNode*>(child);
+      Directionality textNodeDir = GetDirectionFromText(text);
       if (textNodeDir != eDir_NotSet) {
         *aDirectionality = textNodeDir;
-        return static_cast<nsTextNode*>(child);
+        return text;
       }
     }
     child = child->GetNextNode(aRoot);
   }
 
   return nullptr;
 }
 
@@ -1047,31 +1048,31 @@ void SetAncestorDirectionIfAuto(nsTextNo
       // parent chain: none of its ancestors will have their direction set by
       // any of its descendants.
       return;
     }
     parent = GetParentOrHostOrSlot(parent, &crossedShadowBoundary);
   }
 }
 
-bool TextNodeWillChangeDirection(nsIContent* aTextNode, Directionality* aOldDir,
+bool TextNodeWillChangeDirection(nsTextNode* aTextNode, Directionality* aOldDir,
                                  uint32_t aOffset) {
   if (!NodeAffectsDirAutoAncestor(aTextNode)) {
     nsTextNodeDirectionalityMap::EnsureMapIsClearFor(aTextNode);
     return false;
   }
 
   uint32_t firstStrong;
-  *aOldDir = GetDirectionFromText(aTextNode->GetText(), &firstStrong);
+  *aOldDir = GetDirectionFromText(aTextNode, &firstStrong);
   return (aOffset <= firstStrong);
 }
 
 void TextNodeChangedDirection(nsTextNode* aTextNode, Directionality aOldDir,
                               bool aNotify) {
-  Directionality newDir = GetDirectionFromText(aTextNode->GetText());
+  Directionality newDir = GetDirectionFromText(aTextNode);
   if (newDir == eDir_NotSet) {
     if (aOldDir != eDir_NotSet && aTextNode->HasTextNodeDirectionalityMap()) {
       // This node used to have a strong directional character but no
       // longer does. ResetTextNodeDirection() will re-resolve the
       // directionality of any elements whose directionality was
       // determined by this node.
       nsTextNodeDirectionalityMap::ResetTextNodeDirection(aTextNode, aTextNode);
     }
@@ -1097,29 +1098,29 @@ void SetDirectionFromNewTextNode(nsTextN
     return;
   }
 
   nsIContent* parent = GetParentOrHostOrSlot(aTextNode);
   if (parent && parent->NodeOrAncestorHasDirAuto()) {
     aTextNode->SetAncestorHasDirAuto();
   }
 
-  Directionality dir = GetDirectionFromText(aTextNode->GetText());
+  Directionality dir = GetDirectionFromText(aTextNode);
   if (dir != eDir_NotSet) {
     SetAncestorDirectionIfAuto(aTextNode, dir);
   }
 }
 
 void ResetDirectionSetByTextNode(nsTextNode* aTextNode) {
   if (!NodeAffectsDirAutoAncestor(aTextNode)) {
     nsTextNodeDirectionalityMap::EnsureMapIsClearFor(aTextNode);
     return;
   }
 
-  Directionality dir = GetDirectionFromText(aTextNode->GetText());
+  Directionality dir = GetDirectionFromText(aTextNode);
   if (dir != eDir_NotSet && aTextNode->HasTextNodeDirectionalityMap()) {
     nsTextNodeDirectionalityMap::ResetTextNodeDirection(aTextNode, aTextNode);
   }
 }
 
 void SetDirectionalityFromValue(Element* aElement, const nsAString& value,
                                 bool aNotify) {
   Directionality dir =
--- a/dom/base/DirectionalityUtils.h
+++ b/dom/base/DirectionalityUtils.h
@@ -91,17 +91,17 @@ void WalkDescendantsSetDirAuto(mozilla::
 void WalkDescendantsClearAncestorDirAuto(nsIContent* aContent);
 
 /**
  * When the contents of a text node are about to change, retrieve the current
  * directionality of the text
  *
  * @return whether the text node affects the directionality of any element
  */
-bool TextNodeWillChangeDirection(nsIContent* aTextNode, Directionality* aOldDir,
+bool TextNodeWillChangeDirection(nsTextNode* aTextNode, Directionality* aOldDir,
                                  uint32_t aOffset);
 
 /**
  * After the contents of a text node have changed, change the directionality
  * of any elements whose directionality is determined by that node
  */
 void TextNodeChangedDirection(nsTextNode* aTextNode, Directionality aOldDir,
                               bool aNotify);
--- a/dom/base/Selection.cpp
+++ b/dom/base/Selection.cpp
@@ -1469,18 +1469,18 @@ void Selection::SelectFramesForContent(n
   nsIFrame* frame = aContent->GetPrimaryFrame();
   if (!frame) {
     return;
   }
   // The frame could be an SVG text frame, in which case we don't treat it
   // as a text frame.
   if (frame->IsTextFrame()) {
     nsTextFrame* textFrame = static_cast<nsTextFrame*>(frame);
-    textFrame->SetSelectedRange(0, aContent->GetText()->GetLength(), aSelected,
-                                mSelectionType);
+    textFrame->SetSelectedRange(0, textFrame->TextFragment()->GetLength(),
+                                aSelected, mSelectionType);
   } else {
     frame->InvalidateFrameSubtree();  // frame continuations?
   }
 }
 
 // select all content children of aContent
 nsresult Selection::SelectAllFramesForContent(
     PostContentIterator& aPostOrderIter, nsIContent* aContent, bool aSelected) {
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -9052,18 +9052,17 @@ bool nsContentUtils::SerializeNodeToMark
           current = next;
           continue;
         }
         break;
       }
 
       case nsINode::TEXT_NODE:
       case nsINode::CDATA_SECTION_NODE: {
-        const nsTextFragment* text =
-            static_cast<nsIContent*>(current)->GetText();
+        const nsTextFragment* text = &current->AsText()->TextFragment();
         nsIContent* parent = current->GetParent();
         if (ShouldEscape(parent)) {
           AppendEncodedCharacters(text, builder);
         } else {
           builder.Append(text);
         }
         break;
       }
--- a/dom/base/nsRange.cpp
+++ b/dom/base/nsRange.cpp
@@ -2870,17 +2870,17 @@ void nsRange::CollectClientRectsAndText(
     nsCOMPtr<nsINode> node = iter.GetCurrentNode();
     iter.Next();
     nsCOMPtr<nsIContent> content = do_QueryInterface(node);
     if (!content) continue;
     if (content->IsText()) {
       if (node == startContainer) {
         int32_t offset = startContainer == endContainer
                              ? static_cast<int32_t>(aEndOffset)
-                             : content->GetText()->GetLength();
+                             : content->AsText()->TextDataLength();
         GetPartialTextRect(aCollector, aTextList, content,
                            static_cast<int32_t>(aStartOffset), offset,
                            aClampToEdge, aFlushLayout);
         continue;
       } else if (node == endContainer) {
         GetPartialTextRect(aCollector, aTextList, content, 0,
                            static_cast<int32_t>(aEndOffset), aClampToEdge,
                            aFlushLayout);
@@ -2984,17 +2984,17 @@ nsresult nsRange::GetUsedFontFaces(nsLay
     if (!frame) {
       continue;
     }
 
     if (content->IsText()) {
       if (node == startContainer) {
         int32_t offset = startContainer == endContainer
                              ? mEnd.Offset()
-                             : content->GetText()->GetLength();
+                             : content->AsText()->TextDataLength();
         nsLayoutUtils::GetFontFacesForText(frame, mStart.Offset(), offset, true,
                                            aResult, fontFaces, aMaxRanges,
                                            aSkipCollapsedWhitespace);
         continue;
       }
       if (node == endContainer) {
         nsLayoutUtils::GetFontFacesForText(frame, 0, mEnd.Offset(), true,
                                            aResult, fontFaces, aMaxRanges,
--- a/dom/events/ContentEventHandler.cpp
+++ b/dom/events/ContentEventHandler.cpp
@@ -501,63 +501,45 @@ static bool IsMozBR(nsIContent* aContent
 }
 
 static void ConvertToNativeNewlines(nsString& aString) {
 #if defined(XP_WIN)
   aString.ReplaceSubstring(NS_LITERAL_STRING("\n"), NS_LITERAL_STRING("\r\n"));
 #endif
 }
 
-static void AppendString(nsAString& aString, nsIContent* aContent) {
-  NS_ASSERTION(aContent->IsText(), "aContent is not a text node!");
-  const nsTextFragment* text = aContent->GetText();
-  if (!text) {
-    return;
-  }
-  text->AppendTo(aString);
+static void AppendString(nsAString& aString, Text* aText) {
+  aText->TextFragment().AppendTo(aString);
 }
 
-static void AppendSubString(nsAString& aString, nsIContent* aContent,
-                            uint32_t aXPOffset, uint32_t aXPLength) {
-  NS_ASSERTION(aContent->IsText(), "aContent is not a text node!");
-  const nsTextFragment* text = aContent->GetText();
-  if (!text) {
-    return;
-  }
-  text->AppendTo(aString, int32_t(aXPOffset), int32_t(aXPLength));
+static void AppendSubString(nsAString& aString, Text* aText, uint32_t aXPOffset,
+                            uint32_t aXPLength) {
+  aText->TextFragment().AppendTo(aString, int32_t(aXPOffset),
+                                 int32_t(aXPLength));
 }
 
 #if defined(XP_WIN)
-static uint32_t CountNewlinesInXPLength(nsIContent* aContent,
-                                        uint32_t aXPLength) {
-  NS_ASSERTION(aContent->IsText(), "aContent is not a text node!");
-  const nsTextFragment* text = aContent->GetText();
-  if (!text) {
-    return 0;
-  }
+static uint32_t CountNewlinesInXPLength(Text* aText, uint32_t aXPLength) {
+  const nsTextFragment* text = &aText->TextFragment();
   // For automated tests, we should abort on debug build.
   MOZ_ASSERT(aXPLength == UINT32_MAX || aXPLength <= text->GetLength(),
              "aXPLength is out-of-bounds");
   const uint32_t length = std::min(aXPLength, text->GetLength());
   uint32_t newlines = 0;
   for (uint32_t i = 0; i < length; ++i) {
     if (text->CharAt(i) == '\n') {
       ++newlines;
     }
   }
   return newlines;
 }
 
-static uint32_t CountNewlinesInNativeLength(nsIContent* aContent,
+static uint32_t CountNewlinesInNativeLength(Text* aText,
                                             uint32_t aNativeLength) {
-  NS_ASSERTION(aContent->IsText(), "aContent is not a text node!");
-  const nsTextFragment* text = aContent->GetText();
-  if (!text) {
-    return 0;
-  }
+  const nsTextFragment* text = &aText->TextFragment();
   // For automated tests, we should abort on debug build.
   MOZ_ASSERT(
       (aNativeLength == UINT32_MAX || aNativeLength <= text->GetLength() * 2),
       "aNativeLength is unexpected value");
   const uint32_t xpLength = text->GetLength();
   uint32_t newlines = 0;
   for (uint32_t i = 0, nativeOffset = 0;
        i < xpLength && nativeOffset < aNativeLength; ++i, ++nativeOffset) {
@@ -579,27 +561,28 @@ uint32_t ContentEventHandler::GetNativeT
   MOZ_ASSERT(aEndOffset >= aStartOffset,
              "aEndOffset must be equals or larger than aStartOffset");
   if (NS_WARN_IF(!aContent->IsText())) {
     return 0;
   }
   if (aStartOffset == aEndOffset) {
     return 0;
   }
-  return GetTextLength(aContent, LINE_BREAK_TYPE_NATIVE, aEndOffset) -
-         GetTextLength(aContent, LINE_BREAK_TYPE_NATIVE, aStartOffset);
+  return GetTextLength(aContent->AsText(), LINE_BREAK_TYPE_NATIVE, aEndOffset) -
+         GetTextLength(aContent->AsText(), LINE_BREAK_TYPE_NATIVE,
+                       aStartOffset);
 }
 
 /* static */
 uint32_t ContentEventHandler::GetNativeTextLength(nsIContent* aContent,
                                                   uint32_t aMaxLength) {
   if (NS_WARN_IF(!aContent->IsText())) {
     return 0;
   }
-  return GetTextLength(aContent, LINE_BREAK_TYPE_NATIVE, aMaxLength);
+  return GetTextLength(aContent->AsText(), LINE_BREAK_TYPE_NATIVE, aMaxLength);
 }
 
 /* static */
 uint32_t ContentEventHandler::GetNativeTextLengthBefore(nsIContent* aContent,
                                                         nsINode* aRootNode) {
   if (NS_WARN_IF(aContent->IsText())) {
     return 0;
   }
@@ -625,17 +608,17 @@ uint32_t ContentEventHandler::GetTextLen
   MOZ_ASSERT(aContent->IsText());
 
   uint32_t textLengthDifference =
 #if defined(XP_WIN)
       // On Windows, the length of a native newline ("\r\n") is twice the length
       // of the XP newline ("\n"), so XP length is equal to the length of the
       // native offset plus the number of newlines encountered in the string.
       (aLineBreakType == LINE_BREAK_TYPE_NATIVE)
-          ? CountNewlinesInXPLength(aContent, aMaxLength)
+          ? CountNewlinesInXPLength(aContent->AsText(), aMaxLength)
           : 0;
 #else
       // On other platforms, the native and XP newlines are the same.
       0;
 #endif
 
   const nsTextFragment* text = aContent->GetText();
   if (!text) {
@@ -646,17 +629,18 @@ uint32_t ContentEventHandler::GetTextLen
 }
 
 static uint32_t ConvertToXPOffset(nsIContent* aContent,
                                   uint32_t aNativeOffset) {
 #if defined(XP_WIN)
   // On Windows, the length of a native newline ("\r\n") is twice the length of
   // the XP newline ("\n"), so XP offset is equal to the length of the native
   // offset minus the number of newlines encountered in the string.
-  return aNativeOffset - CountNewlinesInNativeLength(aContent, aNativeOffset);
+  return aNativeOffset - CountNewlinesInNativeLength(aContent->AsText(),
+                                                     aNativeOffset);
 #else
   // On other platforms, the native and XP newlines are the same.
   return aNativeOffset;
 #endif
 }
 
 /* static */
 bool ContentEventHandler::ShouldBreakLineBefore(nsIContent* aContent,
@@ -726,18 +710,17 @@ nsresult ContentEventHandler::GenerateFl
 
   nsINode* startNode = aRawRange.GetStartContainer();
   nsINode* endNode = aRawRange.GetEndContainer();
   if (NS_WARN_IF(!startNode) || NS_WARN_IF(!endNode)) {
     return NS_ERROR_FAILURE;
   }
 
   if (startNode == endNode && startNode->IsText()) {
-    nsIContent* content = startNode->AsContent();
-    AppendSubString(aString, content, aRawRange.StartOffset(),
+    AppendSubString(aString, startNode->AsText(), aRawRange.StartOffset(),
                     aRawRange.EndOffset() - aRawRange.StartOffset());
     ConvertToNativeNewlines(aString);
     return NS_OK;
   }
 
   PreContentIterator preOrderIter;
   nsresult rv =
       preOrderIter.Init(aRawRange.Start().AsRaw(), aRawRange.End().AsRaw());
@@ -747,28 +730,27 @@ nsresult ContentEventHandler::GenerateFl
   for (; !preOrderIter.IsDone(); preOrderIter.Next()) {
     nsINode* node = preOrderIter.GetCurrentNode();
     if (NS_WARN_IF(!node)) {
       break;
     }
     if (!node->IsContent()) {
       continue;
     }
-    nsIContent* content = node->AsContent();
 
-    if (content->IsText()) {
-      if (content == startNode) {
-        AppendSubString(aString, content, aRawRange.StartOffset(),
-                        content->TextLength() - aRawRange.StartOffset());
-      } else if (content == endNode) {
-        AppendSubString(aString, content, 0, aRawRange.EndOffset());
+    if (node->IsText()) {
+      if (node == startNode) {
+        AppendSubString(aString, node->AsText(), aRawRange.StartOffset(),
+                        node->AsText()->TextLength() - aRawRange.StartOffset());
+      } else if (node == endNode) {
+        AppendSubString(aString, node->AsText(), 0, aRawRange.EndOffset());
       } else {
-        AppendString(aString, content);
+        AppendString(aString, node->AsText());
       }
-    } else if (ShouldBreakLineBefore(content, mRootContent)) {
+    } else if (ShouldBreakLineBefore(node->AsContent(), mRootContent)) {
       aString.Append(char16_t('\n'));
     }
   }
   if (aLineBreakType == LINE_BREAK_TYPE_NATIVE) {
     ConvertToNativeNewlines(aString);
   }
   return NS_OK;
 }
@@ -996,17 +978,17 @@ nsresult ContentEventHandler::ExpandToCl
     if (textFrame->PeekOffsetCharacter(aForward, &newOffsetInFrame, options) ==
         nsIFrame::FOUND) {
       *aXPOffset = startOffset + newOffsetInFrame;
       return NS_OK;
     }
   }
 
   // If the frame isn't available, we only can check surrogate pair...
-  const nsTextFragment* text = aContent->GetText();
+  const nsTextFragment* text = &aContent->AsText()->TextFragment();
   NS_ENSURE_TRUE(text, NS_ERROR_FAILURE);
   if (NS_IS_LOW_SURROGATE(text->CharAt(*aXPOffset)) &&
       NS_IS_HIGH_SURROGATE(text->CharAt(*aXPOffset - 1))) {
     *aXPOffset += aForward ? 1 : -1;
   }
   return NS_OK;
 }
 
@@ -1859,17 +1841,17 @@ nsresult ContentEventHandler::OnQueryTex
     if (firstFrame->IsTextFrame()) {
       rv = firstFrame->GetCharacterRectsInRange(firstFrame.mOffsetInNode,
                                                 kEndOffset - offset, charRects);
       if (NS_WARN_IF(NS_FAILED(rv)) || NS_WARN_IF(charRects.IsEmpty())) {
         return rv;
       }
       // Assign the characters whose rects are computed by the call of
       // nsTextFrame::GetCharacterRectsInRange().
-      AppendSubString(chars, firstContent, firstFrame.mOffsetInNode,
+      AppendSubString(chars, firstContent->AsText(), firstFrame.mOffsetInNode,
                       charRects.Length());
       if (NS_WARN_IF(chars.Length() != charRects.Length())) {
         return NS_ERROR_UNEXPECTED;
       }
       if (kBRLength > 1 && chars[0] == '\n' &&
           offset == aEvent->mInput.mOffset && offset) {
         // If start of range starting from previous offset of query range is
         // same as the start of query range, the query range starts from
--- a/dom/svg/SVGTextContentElement.cpp
+++ b/dom/svg/SVGTextContentElement.cpp
@@ -78,17 +78,17 @@ Maybe<int32_t> SVGTextContentElement::Ge
 
   uint32_t num = 0;
 
   for (nsINode* n = Element::GetFirstChild(); n; n = n->GetNextSibling()) {
     if (!n->IsText()) {
       return Nothing();
     }
 
-    const nsTextFragment* text = static_cast<nsTextNode*>(n)->GetText();
+    const nsTextFragment* text = &n->AsText()->TextFragment();
     uint32_t length = text->GetLength();
 
     if (text->Is2b()) {
       if (FragmentHasSkippableCharacter(text->Get2b(), length)) {
         return Nothing();
       }
     } else {
       auto buffer = reinterpret_cast<const uint8_t*>(text->Get1b());
--- a/editor/libeditor/HTMLEditRules.cpp
+++ b/editor/libeditor/HTMLEditRules.cpp
@@ -2424,17 +2424,17 @@ nsresult HTMLEditRules::WillDeleteSelect
       if (aAction == nsIEditor::ePrevious) {
         if (!so) {
           return NS_ERROR_UNEXPECTED;
         }
         so--;
         eo--;
         // Bug 1068979: delete both codepoints if surrogate pair
         if (so > 0) {
-          const nsTextFragment* text = nodeAsText->GetText();
+          const nsTextFragment* text = &nodeAsText->TextFragment();
           if (NS_IS_LOW_SURROGATE(text->CharAt(so)) &&
               NS_IS_HIGH_SURROGATE(text->CharAt(so - 1))) {
             so--;
           }
         }
       } else {
         RefPtr<nsRange> range = SelectionRefPtr()->GetRangeAt(0);
         if (NS_WARN_IF(!range)) {
--- a/editor/libeditor/TextEditor.cpp
+++ b/editor/libeditor/TextEditor.cpp
@@ -582,17 +582,17 @@ nsresult TextEditor::ExtendSelectionForD
         }
 
         // node might be anonymous DIV, so we find better text node
         EditorRawDOMPoint insertionPoint =
             FindBetterInsertionPoint(atStartOfSelection);
 
         if (insertionPoint.IsInTextNode()) {
           const nsTextFragment* data =
-              insertionPoint.GetContainerAsText()->GetText();
+              &insertionPoint.GetContainerAsText()->TextFragment();
           uint32_t offset = insertionPoint.Offset();
           if ((offset > 1 && NS_IS_LOW_SURROGATE(data->CharAt(offset - 1)) &&
                NS_IS_HIGH_SURROGATE(data->CharAt(offset - 2))) ||
               (offset > 0 &&
                gfxFontUtils::IsVarSelector(data->CharAt(offset - 1)))) {
             nsresult rv = selCont->CharacterExtendForBackspace();
             if (NS_WARN_IF(NS_FAILED(rv))) {
               return rv;
--- a/editor/libeditor/WSRunObject.cpp
+++ b/editor/libeditor/WSRunObject.cpp
@@ -652,17 +652,17 @@ nsresult WSRunObject::GetWSNodes() {
   // collect up an array of nodes that are contiguous with the insertion point
   // and which contain only whitespace.  Stop if you reach non-ws text or a new
   // block boundary.
   EditorDOMPoint start(mScanStartPoint), end(mScanEndPoint);
   nsCOMPtr<nsINode> wsBoundingParent = GetWSBoundingParent();
 
   // first look backwards to find preceding ws nodes
   if (Text* textNode = mScanStartPoint.GetContainerAsText()) {
-    const nsTextFragment* textFrag = textNode->GetText();
+    const nsTextFragment* textFrag = &textNode->TextFragment();
     mNodeArray.InsertElementAt(0, textNode);
     if (!mScanStartPoint.IsStartOfContainer()) {
       for (uint32_t i = mScanStartPoint.Offset(); i; i--) {
         // sanity bounds check the char position.  bug 136165
         if (i > textFrag->GetLength()) {
           MOZ_ASSERT_UNREACHABLE("looking beyond end of text fragment");
           continue;
         }
@@ -696,20 +696,20 @@ nsresult WSRunObject::GetWSNodes() {
       if (IsBlockNode(priorNode)) {
         mStartNode = start.GetContainer();
         mStartOffset = start.Offset();
         mStartReason = WSType::otherBlock;
         mStartReasonNode = priorNode;
       } else if (priorNode->IsText() && priorNode->IsEditable()) {
         RefPtr<Text> textNode = priorNode->GetAsText();
         mNodeArray.InsertElementAt(0, textNode);
-        const nsTextFragment* textFrag;
-        if (!textNode || !(textFrag = textNode->GetText())) {
+        if (!textNode) {
           return NS_ERROR_NULL_POINTER;
         }
+        const nsTextFragment* textFrag = &textNode->TextFragment();
         uint32_t len = textNode->TextLength();
 
         if (len < 1) {
           // Zero length text node. Set start point to it
           // so we can get past it!
           start.Set(priorNode, 0);
         } else {
           for (int32_t pos = len - 1; pos >= 0; pos--) {
@@ -758,17 +758,17 @@ nsresult WSRunObject::GetWSNodes() {
       mStartReason = WSType::thisBlock;
       mStartReasonNode = wsBoundingParent;
     }
   }
 
   // then look ahead to find following ws nodes
   if (Text* textNode = mScanEndPoint.GetContainerAsText()) {
     // don't need to put it on list. it already is from code above
-    const nsTextFragment* textFrag = textNode->GetText();
+    const nsTextFragment* textFrag = &textNode->TextFragment();
     if (!mScanEndPoint.IsEndOfContainer()) {
       for (uint32_t i = mScanEndPoint.Offset(); i < textNode->TextLength();
            i++) {
         // sanity bounds check the char position.  bug 136165
         if (i >= textFrag->GetLength()) {
           MOZ_ASSERT_UNREACHABLE("looking beyond end of text fragment");
           continue;
         }
@@ -803,20 +803,20 @@ nsresult WSRunObject::GetWSNodes() {
         // we encountered a new block.  therefore no more ws.
         mEndNode = end.GetContainer();
         mEndOffset = end.Offset();
         mEndReason = WSType::otherBlock;
         mEndReasonNode = nextNode;
       } else if (nextNode->IsText() && nextNode->IsEditable()) {
         RefPtr<Text> textNode = nextNode->GetAsText();
         mNodeArray.AppendElement(textNode);
-        const nsTextFragment* textFrag;
-        if (!textNode || !(textFrag = textNode->GetText())) {
+        if (!textNode) {
           return NS_ERROR_NULL_POINTER;
         }
+        const nsTextFragment* textFrag = &textNode->TextFragment();
         uint32_t len = textNode->TextLength();
 
         if (len < 1) {
           // Zero length text node. Set end point to it
           // so we can get past it!
           end.Set(textNode, 0);
         } else {
           for (uint32_t pos = 0; pos < len; pos++) {
@@ -1505,17 +1505,17 @@ nsresult WSRunObject::InsertNBSPAndRemov
       aPoint.mOffset, true);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   // Now, the text node may have been modified by mutation observer.
   // So, the NBSP may have gone.
   if (aPoint.mTextNode->TextDataLength() <= aPoint.mOffset ||
-      aPoint.mTextNode->GetText()->CharAt(aPoint.mOffset) != kNBSP) {
+      aPoint.mTextNode->TextFragment().CharAt(aPoint.mOffset) != kNBSP) {
     // This is just preparation of an edit action.  Let's return NS_OK.
     // XXX Perhaps, we should return another success code which indicates
     //     mutation observer touched the DOM tree.  However, that should
     //     be returned from each transaction's DoTransaction.
     return NS_OK;
   }
 
   // Next, find range of whitespaces it will be replaced.
@@ -1635,21 +1635,21 @@ WSRunObject::WSFragment* WSRunObject::Fi
 
   return nullptr;
 }
 
 char16_t WSRunObject::GetCharAt(Text* aTextNode, int32_t aOffset) const {
   // return 0 if we can't get a char, for whatever reason
   NS_ENSURE_TRUE(aTextNode, 0);
 
-  int32_t len = int32_t(aTextNode->TextLength());
+  int32_t len = int32_t(aTextNode->TextDataLength());
   if (aOffset < 0 || aOffset >= len) {
     return 0;
   }
-  return aTextNode->GetText()->CharAt(aOffset);
+  return aTextNode->TextFragment().CharAt(aOffset);
 }
 
 template <typename PT, typename CT>
 WSRunObject::WSPoint WSRunObject::GetNextCharPointInternal(
     const EditorDOMPointBase<PT, CT>& aPoint) const {
   // Note: only to be called if aPoint.GetContainer() is not a ws node.
 
   // Binary search on wsnodes
--- a/layout/base/RestyleManager.cpp
+++ b/layout/base/RestyleManager.cpp
@@ -281,17 +281,17 @@ void RestyleManager::CharacterDataChange
     // child of the parent element. Fortunately, it's uncommon to have such
     // nodes and this not being an append.
     //
     // See the testcase in bug 1427625 for a test-case that triggers this.
     RestyleForInsertOrChange(aContent);
     return;
   }
 
-  const nsTextFragment* text = aContent->GetText();
+  const nsTextFragment* text = &aContent->AsText()->TextFragment();
 
   const size_t oldLength = aInfo.mChangeStart;
   const size_t newLength = text->GetLength();
 
   const bool emptyChanged = !oldLength && newLength;
 
   const bool whitespaceOnlyChanged =
       text->Is2b()
--- a/layout/base/nsBidiPresUtils.cpp
+++ b/layout/base/nsBidiPresUtils.cpp
@@ -1272,20 +1272,20 @@ bool nsBidiPresUtils::ChildListMayRequir
         // we might need to remove the property set by a previous reflow, if
         // content has changed; see bug 1366623).
         if (frame->HasProperty(nsIFrame::BidiDataProperty())) {
           return true;
         }
 
         // Check whether the text frame has any RTL characters; if so, bidi
         // resolution will be needed.
-        nsIContent* content = frame->GetContent();
+        dom::Text* content = frame->GetContent()->AsText();
         if (content != *aCurrContent) {
           *aCurrContent = content;
-          const nsTextFragment* txt = content->GetText();
+          const nsTextFragment* txt = &content->TextFragment();
           if (txt->Is2b() &&
               HasRTLChars(MakeSpan(txt->Get2b(), txt->GetLength()))) {
             return true;
           }
         }
       }
     } else if (ChildListMayRequireBidi(frame->PrincipalChildList().FirstChild(),
                                        aCurrContent)) {
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -9984,45 +9984,34 @@ static int32_t FirstLetterCount(const ns
       count++;
       break;
     }
   }
 
   return count;
 }
 
-static bool NeedFirstLetterContinuation(nsIContent* aContent) {
-  MOZ_ASSERT(aContent, "null ptr");
-
-  bool result = false;
-  if (aContent) {
-    const nsTextFragment* frag = aContent->GetText();
-    if (frag) {
-      int32_t flc = FirstLetterCount(frag);
-      int32_t tl = frag->GetLength();
-      if (flc < tl) {
-        result = true;
-      }
-    }
-  }
-  return result;
-}
-
-static bool IsFirstLetterContent(nsIContent* aContent) {
-  return aContent->TextLength() && !aContent->TextIsOnlyWhitespace();
+static bool NeedFirstLetterContinuation(Text* aText) {
+  MOZ_ASSERT(aText, "null ptr");
+  int32_t flc = FirstLetterCount(&aText->TextFragment());
+  int32_t tl = aText->TextDataLength();
+  return flc < tl;
+}
+
+static bool IsFirstLetterContent(Text* aText) {
+  return aText->TextDataLength() && !aText->TextIsOnlyWhitespace();
 }
 
 /**
  * Create a letter frame, only make it a floating frame.
  */
 nsFirstLetterFrame* nsCSSFrameConstructor::CreateFloatingLetterFrame(
-    nsFrameConstructorState& aState, nsIContent* aTextContent,
-    nsIFrame* aTextFrame, nsContainerFrame* aParentFrame,
-    ComputedStyle* aParentComputedStyle, ComputedStyle* aComputedStyle,
-    nsFrameList& aResult) {
+    nsFrameConstructorState& aState, Text* aTextContent, nsIFrame* aTextFrame,
+    nsContainerFrame* aParentFrame, ComputedStyle* aParentComputedStyle,
+    ComputedStyle* aComputedStyle, nsFrameList& aResult) {
   MOZ_ASSERT(aParentComputedStyle);
 
   nsFirstLetterFrame* letterFrame =
       NS_NewFirstLetterFrame(mPresShell, aComputedStyle);
   // We don't want to use a text content for a non-text frame (because we want
   // its primary frame to be a text frame).
   nsIContent* letterContent = aParentFrame->GetContent();
   nsContainerFrame* containingBlock =
@@ -10076,19 +10065,17 @@ nsFirstLetterFrame* nsCSSFrameConstructo
 }
 
 /**
  * Create a new letter frame for aTextFrame. The letter frame will be
  * a child of aParentFrame.
  */
 void nsCSSFrameConstructor::CreateLetterFrame(
     nsContainerFrame* aBlockFrame, nsContainerFrame* aBlockContinuation,
-    nsIContent* aTextContent, nsContainerFrame* aParentFrame,
-    nsFrameList& aResult) {
-  MOZ_ASSERT(aTextContent->IsText(), "aTextContent isn't text");
+    Text* aTextContent, nsContainerFrame* aParentFrame, nsFrameList& aResult) {
   NS_ASSERTION(aBlockFrame->IsBlockFrameOrSubclass(), "Not a block frame?");
 
   // Get a ComputedStyle for the first-letter-frame.
   //
   // Keep this in sync with nsBlockFrame::UpdatePseudoElementStyles.
   nsIFrame* parentFrame = nsFrame::CorrectStyleParentFrame(
       aParentFrame, PseudoStyleType::firstLetter);
 
@@ -10212,17 +10199,17 @@ void nsCSSFrameConstructor::WrapFramesIn
   nsIFrame* frame = aParentFrameList;
 
   while (frame) {
     nsIFrame* nextFrame = frame->GetNextSibling();
 
     LayoutFrameType frameType = frame->Type();
     if (LayoutFrameType::Text == frameType) {
       // Wrap up first-letter content in a letter frame
-      nsIContent* textContent = frame->GetContent();
+      Text* textContent = frame->GetContent()->AsText();
       if (IsFirstLetterContent(textContent)) {
         // Create letter frame to wrap up the text
         CreateLetterFrame(aBlockFrame, aBlockContinuation, textContent,
                           aParentFrame, aLetterFrames);
 
         // Provide adjustment information for parent
         *aModifiedParent = aParentFrame;
         *aTextFrame = frame;
--- a/layout/base/nsCSSFrameConstructor.h
+++ b/layout/base/nsCSSFrameConstructor.h
@@ -1915,24 +1915,24 @@ class nsCSSFrameConstructor final : publ
 
   void ReframeContainingBlock(nsIFrame* aFrame);
 
   //----------------------------------------
 
   // Methods support :first-letter style
 
   nsFirstLetterFrame* CreateFloatingLetterFrame(
-      nsFrameConstructorState& aState, nsIContent* aTextContent,
+      nsFrameConstructorState& aState, mozilla::dom::Text* aTextContent,
       nsIFrame* aTextFrame, nsContainerFrame* aParentFrame,
       ComputedStyle* aParentComputedStyle, ComputedStyle* aComputedStyle,
       nsFrameList& aResult);
 
   void CreateLetterFrame(nsContainerFrame* aBlockFrame,
                          nsContainerFrame* aBlockContinuation,
-                         nsIContent* aTextContent,
+                         mozilla::dom::Text* aTextContent,
                          nsContainerFrame* aParentFrame, nsFrameList& aResult);
 
   void WrapFramesInFirstLetterFrame(nsContainerFrame* aBlockFrame,
                                     nsFrameList& aBlockFrames);
 
   /**
    * Looks in the block aBlockFrame for a text frame that contains the
    * first-letter of the block and creates the necessary first-letter frames
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -9358,17 +9358,17 @@ void nsLayoutUtils::GetFrameTextContent(
 
 /* static */
 void nsLayoutUtils::AppendFrameTextContent(nsIFrame* aFrame,
                                            nsAString& aResult) {
   if (aFrame->IsTextFrame()) {
     auto textFrame = static_cast<nsTextFrame*>(aFrame);
     auto offset = textFrame->GetContentOffset();
     auto length = textFrame->GetContentLength();
-    textFrame->GetContent()->GetText()->AppendTo(aResult, offset, length);
+    textFrame->TextFragment()->AppendTo(aResult, offset, length);
   } else {
     for (nsIFrame* child : aFrame->PrincipalChildList()) {
       AppendFrameTextContent(child, aResult);
     }
   }
 }
 
 /* static */
--- a/layout/forms/nsTextControlFrame.cpp
+++ b/layout/forms/nsTextControlFrame.cpp
@@ -885,18 +885,17 @@ nsresult nsTextControlFrame::SelectAllOr
         // Editor won't remove text node when empty value.
         --numChildren;
       }
     }
     if (!aSelect && numChildren) {
       child = child->GetPreviousSibling();
       if (child && child->IsText()) {
         rootNode = child;
-        const nsTextFragment* fragment = child->GetText();
-        numChildren = fragment ? fragment->GetLength() : 0;
+        numChildren = child->AsText()->TextDataLength();
       }
     }
   }
 
   rv = SetSelectionInternal(rootNode, aSelect ? 0 : numChildren, rootNode,
                             numChildren);
   NS_ENSURE_SUCCESS(rv, rv);
 
--- a/layout/generic/nsFontInflationData.cpp
+++ b/layout/generic/nsFontInflationData.cpp
@@ -253,17 +253,17 @@ void nsFontInflationData::UpdateISize(co
         continue;
       }
 
       if (kid->IsTextFrame()) {
         nsIContent* content = kid->GetContent();
         if (content && kid == content->GetPrimaryFrame()) {
           uint32_t len = nsTextFrameUtils::
               ComputeApproximateLengthWithWhitespaceCompression(
-                  content, kid->StyleText());
+                  content->AsText(), kid->StyleText());
           if (len != 0) {
             return kid;
           }
         }
       } else {
         nsIFrame* kidResult = FindEdgeInflatableFrameIn(kid, aDirection);
         if (kidResult) {
           return kidResult;
@@ -290,17 +290,18 @@ static uint32_t DoCharCountOfLargestOpti
       optionResult = DoCharCountOfLargestOption(option);
     } else {
       // REVIEW: Check the frame structure for this!
       optionResult = 0;
       for (nsIFrame* optionChild : option->PrincipalChildList()) {
         if (optionChild->IsTextFrame()) {
           optionResult += nsTextFrameUtils::
               ComputeApproximateLengthWithWhitespaceCompression(
-                  optionChild->GetContent(), optionChild->StyleText());
+                  optionChild->GetContent()->AsText(),
+                  optionChild->StyleText());
         }
       }
     }
     if (optionResult > result) {
       result = optionResult;
     }
   }
   return result;
@@ -329,17 +330,17 @@ void nsFontInflationData::ScanTextIn(nsI
       }
 
       LayoutFrameType fType = kid->Type();
       if (fType == LayoutFrameType::Text) {
         nsIContent* content = kid->GetContent();
         if (content && kid == content->GetPrimaryFrame()) {
           uint32_t len = nsTextFrameUtils::
               ComputeApproximateLengthWithWhitespaceCompression(
-                  content, kid->StyleText());
+                  content->AsText(), kid->StyleText());
           if (len != 0) {
             nscoord fontSize = kid->StyleFont()->mFont.size;
             if (fontSize > 0) {
               mTextAmount += fontSize * len;
             }
           }
         }
       } else if (fType == LayoutFrameType::TextInput) {
--- a/layout/generic/nsTextFrame.cpp
+++ b/layout/generic/nsTextFrame.cpp
@@ -671,17 +671,17 @@ void GlyphObserver::NotifyGlyphsChanged(
   TextRunMappedFlow* userMappedFlows = GetMappedFlows(mTextRun);
   for (uint32_t i = 0; i < data->mMappedFlowCount; ++i) {
     InvalidateFrameDueToGlyphsChanged(userMappedFlows[i].mStartFrame);
   }
 }
 
 int32_t nsTextFrame::GetContentEnd() const {
   nsTextFrame* next = GetNextContinuation();
-  return next ? next->GetContentOffset() : mContent->GetText()->GetLength();
+  return next ? next->GetContentOffset() : TextFragment()->GetLength();
 }
 
 struct FlowLengthProperty {
   int32_t mStartOffset;
   // The offset of the next fixed continuation after mStartOffset, or
   // of the end of the text if there is none
   int32_t mEndFlowOffset;
 };
@@ -711,17 +711,17 @@ int32_t nsTextFrame::GetInFlowContentLen
     NS_ASSERTION(flowLength->mEndFlowOffset >= GetContentEnd(),
                  "frame crosses fixed continuation boundary");
 #endif
     return flowLength->mEndFlowOffset - mContentOffset;
   }
 
   nsTextFrame* nextBidi = LastInFlow()->GetNextContinuation();
   int32_t endFlow =
-      nextBidi ? nextBidi->GetContentOffset() : mContent->TextLength();
+      nextBidi ? nextBidi->GetContentOffset() : GetContent()->TextLength();
 
   if (!flowLength) {
     flowLength = new FlowLengthProperty;
     if (NS_FAILED(mContent->SetProperty(
             nsGkAtoms::flowlength, flowLength,
             nsINode::DeleteProperty<FlowLengthProperty>))) {
       delete flowLength;
       flowLength = nullptr;
@@ -1042,17 +1042,17 @@ class BuildTextRunsScanner {
     // ancestor of the elements containing the characters is the one whose
     // CSS 'white-space' property governs. So this records the nearest common
     // ancestor of mStartFrame and the previous text frame, or null if there
     // was no previous text frame on this line.
     nsIFrame* mAncestorControllingInitialBreak;
 
     int32_t GetContentEnd() {
       return mEndFrame ? mEndFrame->GetContentOffset()
-                       : mStartFrame->GetContent()->GetText()->GetLength();
+                       : mStartFrame->TextFragment()->GetLength();
     }
   };
 
   class BreakSink final : public nsILineBreakSink {
    public:
     BreakSink(gfxTextRun* aTextRun, DrawTarget* aDrawTarget,
               uint32_t aOffsetIntoTextRun)
         : mTextRun(aTextRun),
@@ -1301,17 +1301,17 @@ BuildTextRunsScanner::FindBoundaryResult
   }
 
   if (aFrame == aState->mStopAtFrame) return FB_STOPPED_AT_STOP_FRAME;
 
   if (textFrame) {
     if (aState->mSeenSpaceForLineBreakingOnThisLine) {
       return FB_CONTINUE;
     }
-    const nsTextFragment* frag = textFrame->GetContent()->GetText();
+    const nsTextFragment* frag = textFrame->TextFragment();
     uint32_t start = textFrame->GetContentOffset();
     uint32_t length = textFrame->GetContentLength();
     const void* text;
     if (frag->Is2b()) {
       // It is possible that we may end up removing all whitespace in
       // a piece of text because of The White Space Processing Rules,
       // so we need to transform it before we can check existence of
       // such whitespaces.
@@ -1679,17 +1679,17 @@ void BuildTextRunsScanner::AccumulateRun
     NS_ASSERTION(mMaxTextLength < UINT32_MAX - aFrame->GetContentLength(),
                  "integer overflow");
     if (mMaxTextLength >= UINT32_MAX - aFrame->GetContentLength()) {
       mMaxTextLength = UINT32_MAX;
     } else {
       mMaxTextLength += aFrame->GetContentLength();
     }
   }
-  mDoubleByteText |= aFrame->GetContent()->GetText()->Is2b();
+  mDoubleByteText |= aFrame->TextFragment()->Is2b();
   mLastFrame = aFrame;
   mCommonAncestorWithLastFrame = aFrame->GetParent();
 
   MappedFlow* mappedFlow = &mMappedFlows[mMappedFlows.Length() - 1];
   NS_ASSERTION(mappedFlow->mStartFrame == aFrame ||
                    mappedFlow->GetContentEnd() == aFrame->GetContentOffset(),
                "Overlapping or discontiguous frames => BAD");
   mappedFlow->mEndFrame = aFrame->GetNextContinuation();
@@ -1700,17 +1700,17 @@ void BuildTextRunsScanner::AccumulateRun
   if (mStartOfLine) {
     mLineBreakBeforeFrames.AppendElement(aFrame);
     mStartOfLine = false;
   }
 }
 
 static bool HasTerminalNewline(const nsTextFrame* aFrame) {
   if (aFrame->GetContentLength() == 0) return false;
-  const nsTextFragment* frag = aFrame->GetContent()->GetText();
+  const nsTextFragment* frag = aFrame->TextFragment();
   return frag->CharAt(aFrame->GetContentEnd() - 1) == '\n';
 }
 
 static gfxFont::Metrics GetFirstFontMetrics(gfxFontGroup* aFontGroup,
                                             bool aVerticalMetrics) {
   if (!aFontGroup) return gfxFont::Metrics();
   gfxFont* font = aFontGroup->GetFirstValidFont();
   return font->GetMetrics(aVerticalMetrics ? nsFontMetrics::eVertical
@@ -2239,17 +2239,17 @@ already_AddRefed<gfxTextRun> BuildTextRu
       oldScriptLevel = sstyScriptLevel;
     }
     if (sstyScriptLevel) {
       anyMathMLStyling = true;
     }
 
     // Figure out what content is included in this flow.
     nsIContent* content = f->GetContent();
-    const nsTextFragment* frag = content->GetText();
+    const nsTextFragment* frag = f->TextFragment();
     int32_t contentStart = mappedFlow->mStartFrame->GetContentOffset();
     int32_t contentEnd = mappedFlow->GetContentEnd();
     int32_t contentLength = contentEnd - contentStart;
 
     TextRunMappedFlow* newFlow = &userMappedFlows[i];
     newFlow->mStartFrame = mappedFlow->mStartFrame;
     newFlow->mDOMOffsetToBeforeTransformOffset =
         skipChars.GetOriginalCharCount() -
@@ -2518,18 +2518,17 @@ bool BuildTextRunsScanner::SetupLineBrea
     MappedFlow* mappedFlow = &mMappedFlows[i];
     nsTextFrame* f = mappedFlow->mStartFrame;
 
     const nsStyleText* textStyle = f->StyleText();
     nsTextFrameUtils::CompressionMode compression =
         GetCSSWhitespaceToCompressionMode(f, textStyle);
 
     // Figure out what content is included in this flow.
-    nsIContent* content = f->GetContent();
-    const nsTextFragment* frag = content->GetText();
+    const nsTextFragment* frag = f->TextFragment();
     int32_t contentStart = mappedFlow->mStartFrame->GetContentOffset();
     int32_t contentEnd = mappedFlow->GetContentEnd();
     int32_t contentLength = contentEnd - contentStart;
 
     nsTextFrameUtils::Flags analysisFlags;
     if (frag->Is2b()) {
       NS_ASSERTION(mDoubleByteText, "Wrong buffer char size!");
       char16_t* bufStart = static_cast<char16_t*>(textPtr);
@@ -2574,17 +2573,17 @@ bool BuildTextRunsScanner::SetupLineBrea
 
 static bool HasCompressedLeadingWhitespace(
     nsTextFrame* aFrame, const nsStyleText* aStyleText,
     int32_t aContentEndOffset, const gfxSkipCharsIterator& aIterator) {
   if (!aIterator.IsOriginalCharSkipped()) return false;
 
   gfxSkipCharsIterator iter = aIterator;
   int32_t frameContentOffset = aFrame->GetContentOffset();
-  const nsTextFragment* frag = aFrame->GetContent()->GetText();
+  const nsTextFragment* frag = aFrame->TextFragment();
   while (frameContentOffset < aContentEndOffset &&
          iter.IsOriginalCharSkipped()) {
     if (IsTrimmableSpace(frag, frameContentOffset, aStyleText)) return true;
     ++frameContentOffset;
     iter.AdvanceOriginal(1);
   }
   return false;
 }
@@ -3183,17 +3182,17 @@ class MOZ_STACK_CLASS PropertyProvider f
    */
   PropertyProvider(nsTextFrame* aFrame, const gfxSkipCharsIterator& aStart,
                    nsTextFrame::TextRunType aWhichTextRun,
                    nsFontMetrics* aFontMetrics)
       : mTextRun(aFrame->GetTextRun(aWhichTextRun)),
         mFontGroup(nullptr),
         mFontMetrics(aFontMetrics),
         mTextStyle(aFrame->StyleText()),
-        mFrag(aFrame->GetContent()->GetText()),
+        mFrag(aFrame->TextFragment()),
         mLineContainer(nullptr),
         mFrame(aFrame),
         mStart(aStart),
         mTempIterator(aStart),
         mTabWidths(nullptr),
         mTabWidthsAnalyzedLimit(0),
         mLength(aFrame->GetContentLength()),
         mWordSpacing(WordSpacing(aFrame, mTextRun)),
@@ -4763,17 +4762,17 @@ void nsTextFrame::NotifyNativeAnonymousT
     f->mReflowRequestedForCharDataChange = true;
   }
 
   // Pretend that all the text changed.
   CharacterDataChangeInfo info;
   info.mAppend = false;
   info.mChangeStart = 0;
   info.mChangeEnd = aOldLength;
-  info.mReplaceLength = mContent->TextLength();
+  info.mReplaceLength = GetContent()->TextLength();
   CharacterDataChanged(info);
 }
 
 nsresult nsTextFrame::CharacterDataChanged(
     const CharacterDataChangeInfo& aInfo) {
   if (mContent->HasFlag(NS_HAS_NEWLINE_PROPERTY)) {
     mContent->DeleteProperty(nsGkAtoms::newline);
     mContent->UnsetFlags(NS_HAS_NEWLINE_PROPERTY);
@@ -7061,17 +7060,17 @@ nsIFrame::ContentOffsets nsTextFrame::Ge
     gfxSkipCharsIterator extraCluster(provider.GetStart());
     extraCluster.AdvanceSkipped(charsFit);
 
     bool allowSplitLigature = true;  // Allow selection of partial ligature...
 
     // ...but don't let selection/insertion-point split two Regional Indicator
     // chars that are ligated in the textrun to form a single flag symbol.
     uint32_t offs = extraCluster.GetOriginalOffset();
-    const nsTextFragment* frag = GetContent()->GetText();
+    const nsTextFragment* frag = TextFragment();
     if (offs + 1 < frag->GetLength() &&
         NS_IS_HIGH_SURROGATE(frag->CharAt(offs)) &&
         NS_IS_LOW_SURROGATE(frag->CharAt(offs + 1)) &&
         gfxFontUtils::IsRegionalIndicator(
             SURROGATE_TO_UCS4(frag->CharAt(offs), frag->CharAt(offs + 1)))) {
       allowSplitLigature = false;
       if (extraCluster.GetSkippedOffset() > 1 &&
           !mTextRun->IsLigatureGroupStart(extraCluster.GetSkippedOffset())) {
@@ -7478,17 +7477,17 @@ nsresult nsTextFrame::GetChildFrameConta
 nsIFrame::FrameSearchResult nsTextFrame::PeekOffsetNoAmount(bool aForward,
                                                             int32_t* aOffset) {
   NS_ASSERTION(aOffset && *aOffset <= GetContentLength(),
                "aOffset out of range");
 
   gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
   if (!mTextRun) return CONTINUE_EMPTY;
 
-  TrimmedOffsets trimmed = GetTrimmedOffsets(mContent->GetText());
+  TrimmedOffsets trimmed = GetTrimmedOffsets(TextFragment());
   // Check whether there are nonskipped characters in the trimmmed range
   return (iter.ConvertOriginalToSkipped(trimmed.GetEnd()) >
           iter.ConvertOriginalToSkipped(trimmed.mStart))
              ? FOUND
              : CONTINUE;
 }
 
 /**
@@ -7539,29 +7538,29 @@ class MOZ_STACK_CLASS ClusterIterator {
   nsTextFrame::TrimmedOffsets mTrimmed;
   nsTArray<bool> mWordBreaks;
   bool mHaveWordBreak;
 };
 
 static bool IsAcceptableCaretPosition(const gfxSkipCharsIterator& aIter,
                                       bool aRespectClusters,
                                       const gfxTextRun* aTextRun,
-                                      nsIFrame* aFrame) {
+                                      nsTextFrame* aFrame) {
   if (aIter.IsOriginalCharSkipped()) return false;
   uint32_t index = aIter.GetSkippedOffset();
   if (aRespectClusters && !aTextRun->IsClusterStart(index)) return false;
   if (index > 0) {
     // Check whether the proposed position is in between the two halves of a
     // surrogate pair, before a Variation Selector character, or within a
     // ligated emoji sequence; if so, this is not a valid character boundary.
     // (In the case where we are respecting clusters, we won't actually get
     // this far because the low surrogate is also marked as non-clusterStart
     // so we'll return FALSE above.)
     uint32_t offs = aIter.GetOriginalOffset();
-    const nsTextFragment* frag = aFrame->GetContent()->GetText();
+    const nsTextFragment* frag = aFrame->TextFragment();
     uint32_t ch = frag->CharAt(offs);
 
     if (gfxFontUtils::IsVarSelector(ch) ||
         (NS_IS_LOW_SURROGATE(ch) && offs > 0 &&
          NS_IS_HIGH_SURROGATE(frag->CharAt(offs - 1))) ||
         (!aTextRun->IsLigatureGroupStart(index) &&
          (unicode::GetEmojiPresentation(ch) == unicode::EmojiDefault ||
           (unicode::GetEmojiPresentation(ch) == unicode::TextDefault &&
@@ -7603,17 +7602,17 @@ nsIFrame::FrameSearchResult nsTextFrame:
       return CONTINUE_UNSELECTABLE;
     }
   }
 
   gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
   if (!mTextRun) return CONTINUE_EMPTY;
 
   TrimmedOffsets trimmed =
-      GetTrimmedOffsets(mContent->GetText(), TrimmedOffsetFlags::NoTrimAfter);
+      GetTrimmedOffsets(TextFragment(), TrimmedOffsetFlags::NoTrimAfter);
 
   // A negative offset means "end of frame".
   int32_t startOffset =
       GetContentOffset() + (*aOffset < 0 ? contentLength : *aOffset);
 
   if (!aForward) {
     // If at the beginning of the line, look at the previous continuation
     for (int32_t i = std::min(trimmed.GetEnd(), startOffset) - 1;
@@ -7736,17 +7735,17 @@ ClusterIterator::ClusterIterator(nsTextF
       mHaveWordBreak(false) {
   mIterator = aTextFrame->EnsureTextRun(nsTextFrame::eInflated);
   if (!aTextFrame->GetTextRun(nsTextFrame::eInflated)) {
     mDirection = 0;  // signal failure
     return;
   }
   mIterator.SetOriginalOffset(aPosition);
 
-  mFrag = aTextFrame->GetContent()->GetText();
+  mFrag = aTextFrame->TextFragment();
   mTrimmed = aTextFrame->GetTrimmedOffsets(
       mFrag, aTrimSpaces ? nsTextFrame::TrimmedOffsetFlags::Default
                          : nsTextFrame::TrimmedOffsetFlags::NoTrimAfter |
                                nsTextFrame::TrimmedOffsetFlags::NoTrimBefore);
 
   int32_t textOffset = aTextFrame->GetContentOffset();
   int32_t textLen = aTextFrame->GetContentLength();
   if (!mWordBreaks.AppendElements(textLen + 1)) {
@@ -8044,17 +8043,17 @@ void nsTextFrame::AddInlineMinISizeForFl
       EnsureTextRun(aTextRunType, aRenderingContext->GetDrawTarget(),
                     aData->LineContainer(), aData->mLine, &flowEndInTextRun);
   gfxTextRun* textRun = GetTextRun(aTextRunType);
   if (!textRun) return;
 
   // Pass null for the line container. This will disable tab spacing, but that's
   // OK since we can't really handle tabs for intrinsic sizing anyway.
   const nsStyleText* textStyle = StyleText();
-  const nsTextFragment* frag = mContent->GetText();
+  const nsTextFragment* frag = TextFragment();
 
   // If we're hyphenating, the PropertyProvider needs the actual length;
   // otherwise we can just pass INT32_MAX to mean "all the text"
   int32_t len = INT32_MAX;
   bool hyphenating = frag->GetLength() > 0 &&
                      (textStyle->mHyphens == StyleHyphens::Auto ||
                       (textStyle->mHyphens == StyleHyphens::Manual &&
                        !!(textRun->GetFlags() &
@@ -8241,17 +8240,17 @@ void nsTextFrame::AddInlinePrefISizeForF
                     aData->LineContainer(), aData->mLine, &flowEndInTextRun);
   gfxTextRun* textRun = GetTextRun(aTextRunType);
   if (!textRun) return;
 
   // Pass null for the line container. This will disable tab spacing, but that's
   // OK since we can't really handle tabs for intrinsic sizing anyway.
 
   const nsStyleText* textStyle = StyleText();
-  const nsTextFragment* frag = mContent->GetText();
+  const nsTextFragment* frag = TextFragment();
   PropertyProvider provider(textRun, textStyle, frag, this, iter, INT32_MAX,
                             nullptr, 0, aTextRunType);
 
   // text-combine-upright frame is constantly 1em on inline-axis.
   if (Style()->IsTextCombined()) {
     aData->mCurrentLine += provider.GetFontMetrics()->EmHeight();
     aData->mTrailingWhitespace = 0;
     aData->mLineIsEmpty = false;
@@ -8770,17 +8769,17 @@ void nsTextFrame::ReflowText(nsLineLayou
 
   bool atStartOfLine = aLineLayout.LineAtStart();
   if (atStartOfLine) {
     AddStateBits(TEXT_START_OF_LINE);
   }
 
   uint32_t flowEndInTextRun;
   nsIFrame* lineContainer = aLineLayout.LineContainerFrame();
-  const nsTextFragment* frag = mContent->GetText();
+  const nsTextFragment* frag = TextFragment();
 
   // DOM offsets of the text range we need to measure, after trimming
   // whitespace, restricting to first-letter, and restricting preformatted text
   // to nearest newline
   int32_t length = maxContentLength;
   int32_t offset = GetContentOffset();
 
   // Restrict preformatted text to the nearest newline
@@ -8794,17 +8793,17 @@ void nsTextFrame::ReflowText(nsLineLayou
                                     mContent->GetProperty(nsGkAtoms::newline))
                               : nullptr;
     if (cachedNewlineOffset && cachedNewlineOffset->mStartOffset <= offset &&
         (cachedNewlineOffset->mNewlineOffset == -1 ||
          cachedNewlineOffset->mNewlineOffset >= offset)) {
       contentNewLineOffset = cachedNewlineOffset->mNewlineOffset;
     } else {
       contentNewLineOffset =
-          FindChar(frag, offset, mContent->TextLength() - offset, '\n');
+          FindChar(frag, offset, GetContent()->TextLength() - offset, '\n');
     }
     if (contentNewLineOffset < offset + length) {
       /*
         The new line offset could be outside this frame if the frame has been
         split by bidi resolution. In that case we won't use it in this reflow
         (newLineOffset will remain -1), but we will still cache it in mContent
       */
       newLineOffset = contentNewLineOffset;
@@ -9344,17 +9343,17 @@ nsTextFrame::TrimOutput nsTextFrame::Tri
   if (!contentLength) return result;
 
   gfxSkipCharsIterator start =
       EnsureTextRun(nsTextFrame::eInflated, aDrawTarget);
   NS_ENSURE_TRUE(mTextRun, result);
 
   uint32_t trimmedStart = start.GetSkippedOffset();
 
-  const nsTextFragment* frag = mContent->GetText();
+  const nsTextFragment* frag = TextFragment();
   TrimmedOffsets trimmed = GetTrimmedOffsets(frag);
   gfxSkipCharsIterator trimmedEndIter = start;
   const nsStyleText* textStyle = StyleText();
   gfxFloat delta = 0;
   uint32_t trimmedEnd =
       trimmedEndIter.ConvertOriginalToSkipped(trimmed.GetEnd());
 
   if (!(GetStateBits() & TEXT_TRIMMED_TRAILING_WHITESPACE) &&
@@ -9512,17 +9511,17 @@ nsIFrame::RenderedText nsTextFrame::GetR
                   aEndOffset <= (uint32_t)GetContentEnd()),
              "Must be called on first-in-flow, or content offsets must be "
              "given and be within this frame.");
 
   // The handling of offsets could be more efficient...
   RenderedText result;
   nsBlockFrame* lineContainer = nullptr;
   nsTextFrame* textFrame;
-  const nsTextFragment* textFrag = mContent->GetText();
+  const nsTextFragment* textFrag = TextFragment();
   uint32_t offsetInRenderedString = 0;
   bool haveOffsets = false;
 
   Maybe<nsBlockFrame::AutoLineCursorSetup> autoLineCursor;
   for (textFrame = this; textFrame;
        textFrame = textFrame->GetNextContinuation()) {
     if (textFrame->GetStateBits() & NS_FRAME_IS_DIRTY) {
       // We don't trust dirty frames, especially when computing rendered text.
@@ -9688,29 +9687,29 @@ bool nsTextFrame::IsEmpty() {
   if (mState & TEXT_ISNOT_ONLY_WHITESPACE) {
     return false;
   }
 
   if (mState & TEXT_IS_ONLY_WHITESPACE) {
     return true;
   }
 
-  bool isEmpty = IsAllWhitespace(
-      mContent->GetText(),
-      textStyle->mWhiteSpace != mozilla::StyleWhiteSpace::PreLine);
+  bool isEmpty =
+      IsAllWhitespace(TextFragment(), textStyle->mWhiteSpace !=
+                                           mozilla::StyleWhiteSpace::PreLine);
   AddStateBits(isEmpty ? TEXT_IS_ONLY_WHITESPACE : TEXT_ISNOT_ONLY_WHITESPACE);
   return isEmpty;
 }
 
 #ifdef DEBUG_FRAME_DUMP
 // Translate the mapped content into a string that's printable
 void nsTextFrame::ToCString(nsCString& aBuf,
                             int32_t* aTotalContentLength) const {
   // Get the frames text content
-  const nsTextFragment* frag = mContent->GetText();
+  const nsTextFragment* frag = TextFragment();
   if (!frag) {
     return;
   }
 
   // Compute the total length of the text content.
   *aTotalContentLength = frag->GetLength();
 
   int32_t contentLength = GetContentLength();
@@ -9881,17 +9880,17 @@ mozilla::JustificationAssignment nsTextF
   int32_t encoded = GetProperty(JustificationAssignmentProperty());
   mozilla::JustificationAssignment result;
   result.mGapsAtStart = encoded >> 8;
   result.mGapsAtEnd = encoded & 0xFF;
   return result;
 }
 
 uint32_t nsTextFrame::CountGraphemeClusters() const {
-  const nsTextFragment* frag = GetContent()->GetText();
+  const nsTextFragment* frag = TextFragment();
   MOZ_ASSERT(frag, "Text frame must have text fragment");
   nsAutoString content;
   frag->AppendTo(content, GetContentOffset(), GetContentLength());
   return unicode::CountGraphemeClusters(content.Data(), content.Length());
 }
 
 bool nsTextFrame::HasNonSuppressedText() {
   if (HasAnyStateBits(TEXT_ISNOT_ONLY_WHITESPACE |
@@ -9901,11 +9900,11 @@ bool nsTextFrame::HasNonSuppressedText()
     return true;
   }
 
   if (!GetTextRun(nsTextFrame::eInflated)) {
     return false;
   }
 
   TrimmedOffsets offsets =
-      GetTrimmedOffsets(mContent->GetText(), TrimmedOffsetFlags::NoTrimAfter);
+      GetTrimmedOffsets(TextFragment(), TrimmedOffsetFlags::NoTrimAfter);
   return offsets.mLength != 0;
 }
--- a/layout/generic/nsTextFrame.h
+++ b/layout/generic/nsTextFrame.h
@@ -6,17 +6,17 @@
 
 #ifndef nsTextFrame_h__
 #define nsTextFrame_h__
 
 #include "mozilla/Attributes.h"
 #include "mozilla/EventForwards.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/UniquePtr.h"
-#include "mozilla/dom/CharacterData.h"
+#include "mozilla/dom/Text.h"
 #include "nsFrame.h"
 #include "nsFrameSelection.h"
 #include "nsSplittableFrame.h"
 #include "nsLineBox.h"
 #include "gfxSkipChars.h"
 #include "gfxTextRun.h"
 #include "nsDisplayList.h"
 #include "nsFontMetrics.h"
@@ -152,16 +152,23 @@ class nsTextFrame : public nsFrame {
 
 #ifdef DEBUG_FRAME_DUMP
   void List(FILE* out = stderr, const char* aPrefix = "",
             uint32_t aFlags = 0) const final;
   nsresult GetFrameName(nsAString& aResult) const final;
   void ToCString(nsCString& aBuf, int32_t* aTotalContentLength) const;
 #endif
 
+  // Returns this text frame's content's text fragment.
+  //
+  // Assertions in Init() ensure we only ever get a Text node as content.
+  const nsTextFragment* TextFragment() const {
+    return &mContent->AsText()->TextFragment();
+  }
+
   ContentOffsets CalcContentOffsetsFromFramePoint(const nsPoint& aPoint) final;
   ContentOffsets GetCharacterOffsetAtFramePoint(const nsPoint& aPoint);
 
   /**
    * This is called only on the primary text frame. It indicates that
    * the selection state of the given character range has changed.
    * Text in the range is unconditionally invalidated
    * (Selection::Repaint depends on this).
--- a/layout/generic/nsTextFrameUtils.cpp
+++ b/layout/generic/nsTextFrameUtils.cpp
@@ -335,18 +335,18 @@ template char16_t* nsTextFrameUtils::Tra
     CompressionMode aCompression, uint8_t* aIncomingFlags,
     gfxSkipChars* aSkipChars, Flags* aAnalysisFlags);
 template bool nsTextFrameUtils::IsSkippableCharacterForTransformText(
     uint8_t aChar);
 template bool nsTextFrameUtils::IsSkippableCharacterForTransformText(
     char16_t aChar);
 
 uint32_t nsTextFrameUtils::ComputeApproximateLengthWithWhitespaceCompression(
-    nsIContent* aContent, const nsStyleText* aStyleText) {
-  const nsTextFragment* frag = aContent->GetText();
+    Text* aText, const nsStyleText* aStyleText) {
+  const nsTextFragment* frag = &aText->TextFragment();
   // This is an approximation so we don't really need anything
   // too fancy here.
   uint32_t len;
   if (aStyleText->WhiteSpaceIsSignificant()) {
     len = frag->GetLength();
   } else {
     bool is2b = frag->Is2b();
     union {
--- a/layout/generic/nsTextFrameUtils.h
+++ b/layout/generic/nsTextFrameUtils.h
@@ -8,16 +8,22 @@
 #define NSTEXTFRAMEUTILS_H_
 
 #include "gfxSkipChars.h"
 #include "nsBidiUtils.h"
 
 class nsIContent;
 struct nsStyleText;
 
+namespace mozilla {
+namespace dom {
+class Text;
+}
+}  // namespace mozilla
+
 #define BIG_TEXT_NODE_SIZE 4096
 
 #define CH_NBSP 160
 #define CH_SHY 173
 #define CH_CJKSP 12288  // U+3000 IDEOGRAPHIC SPACE (CJK Full-Width Space)
 
 class nsTextFrameUtils {
  public:
@@ -128,17 +134,17 @@ class nsTextFrameUtils {
   static void AppendLineBreakOffset(nsTArray<uint32_t>* aArray,
                                     uint32_t aOffset) {
     if (aArray->Length() > 0 && (*aArray)[aArray->Length() - 1] == aOffset)
       return;
     aArray->AppendElement(aOffset);
   }
 
   static uint32_t ComputeApproximateLengthWithWhitespaceCompression(
-      nsIContent* aContent, const nsStyleText* aStyleText);
+      mozilla::dom::Text* aText, const nsStyleText* aStyleText);
 };
 
 MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(nsTextFrameUtils::Flags)
 
 class nsSkipCharsRunIterator {
  public:
   enum LengthMode {
     LENGTH_UNSKIPPED_ONLY = false,
--- a/layout/svg/SVGTextFrame.cpp
+++ b/layout/svg/SVGTextFrame.cpp
@@ -975,17 +975,17 @@ void TextRenderedRun::GetClipEdges(nscoo
   // Get the offset/length of the whole nsTextFrame.
   uint32_t frameOffset = mFrame->GetContentOffset();
   uint32_t frameLength = mFrame->GetContentLength();
 
   // Trim the whole-nsTextFrame offset/length to remove any leading/trailing
   // white space, as the nsTextFrame when painting does not include them when
   // interpreting clip edges.
   nsTextFrame::TrimmedOffsets trimmedOffsets =
-      mFrame->GetTrimmedOffsets(mFrame->GetContent()->GetText());
+      mFrame->GetTrimmedOffsets(mFrame->TextFragment());
   TrimOffsets(frameOffset, frameLength, trimmedOffsets);
 
   // Convert the trimmed whole-nsTextFrame offset/length into skipped
   // characters.
   Range frameRange = ConvertOriginalToSkipped(it, frameOffset, frameLength);
 
   // Measure the advance width in the text run between the start of
   // frame's content and the start of the rendered run's content,
@@ -1880,17 +1880,17 @@ TextRenderedRun TextRenderedRunIterator:
     baseline = GetBaselinePosition(
         frame, frame->GetTextRun(nsTextFrame::eInflated),
         mFrameIterator.DominantBaseline(), mFontSizeScaleFactor);
 
     // Trim the offset/length to remove any leading/trailing white space.
     uint32_t untrimmedOffset = offset;
     uint32_t untrimmedLength = length;
     nsTextFrame::TrimmedOffsets trimmedOffsets =
-        frame->GetTrimmedOffsets(frame->GetContent()->GetText());
+        frame->GetTrimmedOffsets(frame->TextFragment());
     TrimOffsets(offset, length, trimmedOffsets);
     charIndex += offset - untrimmedOffset;
 
     // Get the position and rotation of the character that begins this
     // rendered run.
     pt = Root()->mPositions[charIndex].mPosition;
     rotate = Root()->mPositions[charIndex].mAngle;
 
@@ -2359,35 +2359,34 @@ bool CharIterator::IsClusterAndLigatureG
 
 bool CharIterator::IsOriginalCharTrimmed() const {
   if (mFrameForTrimCheck != TextFrame()) {
     // Since we do a lot of trim checking, we cache the trimmed offsets and
     // lengths while we are in the same frame.
     mFrameForTrimCheck = TextFrame();
     uint32_t offset = mFrameForTrimCheck->GetContentOffset();
     uint32_t length = mFrameForTrimCheck->GetContentLength();
-    nsIContent* content = mFrameForTrimCheck->GetContent();
     nsTextFrame::TrimmedOffsets trim = mFrameForTrimCheck->GetTrimmedOffsets(
-        content->GetText(),
+        mFrameForTrimCheck->TextFragment(),
         (mPostReflow ? nsTextFrame::TrimmedOffsetFlags::Default
                      : nsTextFrame::TrimmedOffsetFlags::NotPostReflow));
     TrimOffsets(offset, length, trim);
     mTrimmedOffset = offset;
     mTrimmedLength = length;
   }
 
   // A character is trimmed if it is outside the mTrimmedOffset/mTrimmedLength
   // range and it is not a significant newline character.
   uint32_t index = mSkipCharsIterator.GetOriginalOffset();
   return !(
       (index >= mTrimmedOffset && index < mTrimmedOffset + mTrimmedLength) ||
       (index >= mTrimmedOffset + mTrimmedLength &&
        mFrameForTrimCheck->StyleText()->NewlineIsSignificant(
            mFrameForTrimCheck) &&
-       mFrameForTrimCheck->GetContent()->GetText()->CharAt(index) == '\n'));
+       mFrameForTrimCheck->TextFragment()->CharAt(index) == '\n'));
 }
 
 void CharIterator::GetOriginalGlyphOffsets(uint32_t& aOriginalOffset,
                                            uint32_t& aOriginalLength) const {
   gfxSkipCharsIterator it = TextFrame()->EnsureTextRun(nsTextFrame::eInflated);
   it.SetOriginalOffset(mSkipCharsIterator.GetOriginalOffset() -
                        (mTextElementCharIndex -
                         mGlyphStartTextElementCharIndex -
@@ -3799,18 +3798,17 @@ nsresult SVGTextFrame::GetSubStringLengt
     // Offset into frame's nsTextNode:
     const uint32_t untrimmedOffset = frame->GetContentOffset();
     const uint32_t untrimmedLength = frame->GetContentEnd() - untrimmedOffset;
 
     // Trim the offset/length to remove any leading/trailing white space.
     uint32_t trimmedOffset = untrimmedOffset;
     uint32_t trimmedLength = untrimmedLength;
     nsTextFrame::TrimmedOffsets trimmedOffsets = frame->GetTrimmedOffsets(
-        frame->GetContent()->GetText(),
-        nsTextFrame::TrimmedOffsetFlags::NotPostReflow);
+        frame->TextFragment(), nsTextFrame::TrimmedOffsetFlags::NotPostReflow);
     TrimOffsets(trimmedOffset, trimmedLength, trimmedOffsets);
 
     textElementCharIndex += trimmedOffset - untrimmedOffset;
 
     if (textElementCharIndex >= charnum + nchars) {
       break;  // we're past the end of the substring
     }
 
@@ -4409,17 +4407,17 @@ void SVGTextFrame::DetermineCharPosition
 
     // Any characters not in a frame, e.g. when display:none.
     for (uint32_t i = 0; i < frit.UndisplayedCharacters(); i++) {
       aPositions.AppendElement(position);
     }
 
     // Any white space characters trimmed at the start of the line of text.
     nsTextFrame::TrimmedOffsets trimmedOffsets =
-        frame->GetTrimmedOffsets(frame->GetContent()->GetText());
+        frame->GetTrimmedOffsets(frame->TextFragment());
     while (it.GetOriginalOffset() < trimmedOffsets.mStart) {
       aPositions.AppendElement(position);
       it.AdvanceOriginal(1);
     }
 
     // If a ligature was started in the previous frame, we should record
     // the ligature's start position, not any partial position.
     while (it.GetOriginalOffset() < frame->GetContentEnd() &&