Bug 1251995 part 2 - Add gfxTextRun::Range to replace parameter pairs like (offset, length) and (start, end). draft
authorXidorn Quan <quanxunzhen@gmail.com>
Mon, 29 Feb 2016 16:50:17 +0800
changeset 337290 9d10294b3ee2c0496e8a7068b5db243c1543b2d1
parent 337289 03de7073890bfbbb4a037889430d3a8f6cde97d5
child 337291 33fa9d147e80b4a2b96ca857401dbe698f551df5
push id12307
push userxquan@mozilla.com
push dateMon, 07 Mar 2016 02:21:17 +0000
bugs1251995
milestone47.0a1
Bug 1251995 part 2 - Add gfxTextRun::Range to replace parameter pairs like (offset, length) and (start, end). Although this makes some places more complicated, code should generally be simpler and clearer. It could slightly improve performance on x64 for functions with more than four parameters, since it makes more data be passed via registers rather than the stack. MozReview-Commit-ID: D0GM2Jyrr6W
dom/events/ContentEventHandler.cpp
gfx/src/nsFontMetrics.cpp
gfx/src/nsFontMetrics.h
gfx/thebes/gfxFont.cpp
gfx/thebes/gfxTextRun.cpp
gfx/thebes/gfxTextRun.h
layout/generic/MathMLTextRunFactory.cpp
layout/generic/TextOverflow.cpp
layout/generic/nsTextFrame.cpp
layout/generic/nsTextFrame.h
layout/generic/nsTextRunTransformations.cpp
layout/generic/nsTextRunTransformations.h
layout/inspector/nsFontFaceList.cpp
layout/mathml/nsMathMLChar.cpp
layout/mathml/nsMathMLChar.h
layout/svg/SVGTextFrame.cpp
layout/svg/SVGTextFrame.h
--- a/dom/events/ContentEventHandler.cpp
+++ b/dom/events/ContentEventHandler.cpp
@@ -653,20 +653,19 @@ ContentEventHandler::AppendFontRanges(Fo
       next = static_cast<nsTextFrame*>(curr->GetNextContinuation());
       while (next && next->GetTextRun(nsTextFrame::eInflated) == textRun) {
         frameXPEnd = std::min(next->GetContentEnd(), aXPEndOffset);
         next = frameXPEnd < aXPEndOffset ?
           static_cast<nsTextFrame*>(next->GetNextContinuation()) : nullptr;
       }
     }
 
-    uint32_t skipStart = iter.ConvertOriginalToSkipped(frameXPStart);
-    uint32_t skipEnd = iter.ConvertOriginalToSkipped(frameXPEnd);
-    gfxTextRun::GlyphRunIterator runIter(
-      textRun, skipStart, skipEnd - skipStart);
+    gfxTextRun::Range skipRange(iter.ConvertOriginalToSkipped(frameXPStart),
+                                iter.ConvertOriginalToSkipped(frameXPEnd));
+    gfxTextRun::GlyphRunIterator runIter(textRun, skipRange);
     int32_t lastXPEndOffset = frameXPStart;
     while (runIter.NextRun()) {
       gfxFont* font = runIter.GetGlyphRun()->mFont.get();
       int32_t startXPOffset =
         iter.ConvertSkippedToOriginal(runIter.GetStringStart());
       // It is possible that the first glyph run has exceeded the frame,
       // because the whole frame is filled by skipped chars.
       if (startXPOffset >= frameXPEnd) {
--- a/gfx/src/nsFontMetrics.cpp
+++ b/gfx/src/nsFontMetrics.cpp
@@ -76,17 +76,17 @@ private:
         return flags;
     }
 
     nsAutoPtr<gfxTextRun> mTextRun;
 };
 
 class StubPropertyProvider : public gfxTextRun::PropertyProvider {
 public:
-    virtual void GetHyphenationBreaks(uint32_t aStart, uint32_t aLength,
+    virtual void GetHyphenationBreaks(gfxTextRun::Range aRange,
                                       bool* aBreakBefore) {
         NS_ERROR("This shouldn't be called because we never call BreakAndMeasureText");
     }
     virtual int8_t GetHyphensOption() {
         NS_ERROR("This shouldn't be called because we never call BreakAndMeasureText");
         return NS_STYLE_HYPHENS_NONE;
     }
     virtual gfxFloat GetHyphenWidth() {
@@ -96,18 +96,17 @@ public:
     virtual already_AddRefed<mozilla::gfx::DrawTarget> GetDrawTarget() {
         NS_ERROR("This shouldn't be called because we never enable hyphens");
         return nullptr;
     }
     virtual uint32_t GetAppUnitsPerDevUnit() {
         NS_ERROR("This shouldn't be called because we never enable hyphens");
         return 60;
     }
-    virtual void GetSpacing(uint32_t aStart, uint32_t aLength,
-                            Spacing* aSpacing) {
+    virtual void GetSpacing(gfxTextRun::Range aRange, Spacing* aSpacing) {
         NS_ERROR("This shouldn't be called because we never enable spacing");
     }
 };
 
 } // namespace
 
 nsFontMetrics::nsFontMetrics()
     : mDeviceContext(nullptr), mP2A(0), mTextRunRTL(false)
@@ -320,34 +319,40 @@ nsFontMetrics::GetWidth(const char* aStr
     if (aLength == 0)
         return 0;
 
     if (aLength == 1 && aString[0] == ' ')
         return SpaceWidth();
 
     StubPropertyProvider provider;
     AutoTextRun textRun(this, aDrawTarget, aString, aLength);
-    return textRun.get() ?
-        NSToCoordRound(textRun->GetAdvanceWidth(0, aLength, &provider)) : 0;
+    if (textRun.get()) {
+      return NSToCoordRound(
+          textRun->GetAdvanceWidth(Range(0, aLength), &provider));
+    }
+    return 0;
 }
 
 nscoord
 nsFontMetrics::GetWidth(const char16_t* aString, uint32_t aLength,
                         DrawTarget* aDrawTarget)
 {
     if (aLength == 0)
         return 0;
 
     if (aLength == 1 && aString[0] == ' ')
         return SpaceWidth();
 
     StubPropertyProvider provider;
     AutoTextRun textRun(this, aDrawTarget, aString, aLength);
-    return textRun.get() ?
-        NSToCoordRound(textRun->GetAdvanceWidth(0, aLength, &provider)) : 0;
+    if (textRun.get()) {
+      return NSToCoordRound(
+          textRun->GetAdvanceWidth(Range(0, aLength), &provider));
+    }
+    return 0;
 }
 
 // Draw a string using this font handle on the surface passed in.
 void
 nsFontMetrics::DrawString(const char *aString, uint32_t aLength,
                           nscoord aX, nscoord aY,
                           nsRenderingContext *aContext)
 {
@@ -355,25 +360,26 @@ nsFontMetrics::DrawString(const char *aS
         return;
 
     StubPropertyProvider provider;
     AutoTextRun textRun(this, aContext->GetDrawTarget(), aString, aLength);
     if (!textRun.get()) {
         return;
     }
     gfxPoint pt(aX, aY);
+    Range range(0, aLength);
     if (mTextRunRTL) {
         if (mVertical) {
-            pt.y += textRun->GetAdvanceWidth(0, aLength, &provider);
+            pt.y += textRun->GetAdvanceWidth(range, &provider);
         } else {
-            pt.x += textRun->GetAdvanceWidth(0, aLength, &provider);
+            pt.x += textRun->GetAdvanceWidth(range, &provider);
         }
     }
-    textRun->Draw(aContext->ThebesContext(), pt, DrawMode::GLYPH_FILL, 0, aLength,
-                  &provider, nullptr, nullptr);
+    textRun->Draw(aContext->ThebesContext(), pt, DrawMode::GLYPH_FILL,
+                  range, &provider, nullptr, nullptr);
 }
 
 void
 nsFontMetrics::DrawString(const char16_t* aString, uint32_t aLength,
                           nscoord aX, nscoord aY,
                           nsRenderingContext *aContext,
                           DrawTarget* aTextRunConstructionDrawTarget)
 {
@@ -381,41 +387,42 @@ nsFontMetrics::DrawString(const char16_t
         return;
 
     StubPropertyProvider provider;
     AutoTextRun textRun(this, aTextRunConstructionDrawTarget, aString, aLength);
     if (!textRun.get()) {
         return;
     }
     gfxPoint pt(aX, aY);
+    Range range(0, aLength);
     if (mTextRunRTL) {
         if (mVertical) {
-            pt.y += textRun->GetAdvanceWidth(0, aLength, &provider);
+            pt.y += textRun->GetAdvanceWidth(range, &provider);
         } else {
-            pt.x += textRun->GetAdvanceWidth(0, aLength, &provider);
+            pt.x += textRun->GetAdvanceWidth(range, &provider);
         }
     }
-    textRun->Draw(aContext->ThebesContext(), pt, DrawMode::GLYPH_FILL, 0, aLength,
-                  &provider, nullptr, nullptr);
+    textRun->Draw(aContext->ThebesContext(), pt, DrawMode::GLYPH_FILL,
+                  range, &provider, nullptr, nullptr);
 }
 
 static nsBoundingMetrics
 GetTextBoundingMetrics(nsFontMetrics* aMetrics, const char16_t* aString,
                        uint32_t aLength, mozilla::gfx::DrawTarget* aDrawTarget,
                        gfxFont::BoundingBoxType aType)
 {
     if (aLength == 0)
         return nsBoundingMetrics();
 
     StubPropertyProvider provider;
     AutoTextRun textRun(aMetrics, aDrawTarget, aString, aLength);
     nsBoundingMetrics m;
     if (textRun.get()) {
-        gfxTextRun::Metrics theMetrics =
-            textRun->MeasureText(0, aLength, aType, aDrawTarget, &provider);
+        gfxTextRun::Metrics theMetrics = textRun->MeasureText(
+            gfxTextRun::Range(0, aLength), aType, aDrawTarget, &provider);
 
         m.leftBearing  = NSToCoordFloor( theMetrics.mBoundingBox.X());
         m.rightBearing = NSToCoordCeil(  theMetrics.mBoundingBox.XMost());
         m.ascent       = NSToCoordCeil( -theMetrics.mBoundingBox.Y());
         m.descent      = NSToCoordCeil(  theMetrics.mBoundingBox.YMost());
         m.width        = NSToCoordRound( theMetrics.mAdvanceWidth);
     }
     return m;
--- a/gfx/src/nsFontMetrics.h
+++ b/gfx/src/nsFontMetrics.h
@@ -41,16 +41,17 @@ struct nsBoundingMetrics;
  * the em height (for the CSS "em" unit), and we use the first Western
  * font's metrics for that purpose. The platform-specific
  * implementations are expected to select non-Western fonts that "fit"
  * reasonably well with the Western font that is loaded at Init time.
  */
 class nsFontMetrics final
 {
 public:
+    typedef gfxTextRun::Range Range;
     typedef mozilla::gfx::DrawTarget DrawTarget;
 
     nsFontMetrics();
 
     NS_INLINE_DECL_REFCOUNTING(nsFontMetrics)
 
     /**
      * Initialize the font metrics. Call this after creating the font metrics.
--- a/gfx/thebes/gfxFont.cpp
+++ b/gfx/thebes/gfxFont.cpp
@@ -1938,17 +1938,17 @@ gfxFont::DrawGlyphs(gfxShapedText       
 
 // This method is mostly parallel to DrawGlyphs.
 void
 gfxFont::DrawEmphasisMarks(gfxTextRun* aShapedText, gfxPoint* aPt,
                            uint32_t aOffset, uint32_t aCount,
                            const EmphasisMarkDrawParams& aParams)
 {
     gfxFloat& inlineCoord = aParams.isVertical ? aPt->y : aPt->x;
-    uint32_t markLength = aParams.mark->GetLength();
+    gfxTextRun::Range markRange(aParams.mark);
 
     gfxFloat clusterStart = -std::numeric_limits<gfxFloat>::infinity();
     bool shouldDrawEmphasisMark = false;
     for (uint32_t i = 0, idx = aOffset; i < aCount; ++i, ++idx) {
         if (aParams.spacing) {
             inlineCoord += aParams.direction * aParams.spacing[i].mBefore;
         }
         if (aShapedText->IsClusterStart(idx) ||
@@ -1961,17 +1961,17 @@ gfxFont::DrawEmphasisMarks(gfxTextRun* a
         inlineCoord += aParams.direction * aShapedText->GetAdvanceForGlyph(idx);
         if (shouldDrawEmphasisMark &&
             (i + 1 == aCount || aShapedText->IsClusterStart(idx + 1))) {
             gfxFloat clusterAdvance = inlineCoord - clusterStart;
             // Move the coord backward to get the needed start point.
             gfxFloat delta = (clusterAdvance + aParams.advance) / 2;
             inlineCoord -= delta;
             aParams.mark->Draw(aParams.context, *aPt, DrawMode::GLYPH_FILL,
-                               0, markLength, nullptr, nullptr, nullptr);
+                               markRange, nullptr, nullptr, nullptr);
             inlineCoord += delta;
             shouldDrawEmphasisMark = false;
         }
         if (aParams.spacing) {
             inlineCoord += aParams.direction * aParams.spacing[i].mAfter;
         }
     }
 }
@@ -3182,17 +3182,18 @@ gfxFont::InitFakeSmallCapsRun(DrawTarget
                     } else {
                         nsAutoPtr<gfxTextRun> mergedRun;
                         mergedRun =
                             gfxTextRun::Create(&params, runLength,
                                                aTextRun->GetFontGroup(), 0);
                         MergeCharactersInTextRun(mergedRun, tempRun,
                                                  charsToMergeArray.Elements(),
                                                  deletedCharsArray.Elements());
-                        aTextRun->CopyGlyphDataFrom(mergedRun, 0, runLength,
+                        gfxTextRun::Range runRange(0, runLength);
+                        aTextRun->CopyGlyphDataFrom(mergedRun, runRange,
                                                     aOffset + runStart);
                     }
                 } else {
                     aTextRun->AddGlyphRun(f, aMatchType, aOffset + runStart,
                                           true, aOrientation);
                     if (!f->SplitAndInitTextRun(aDrawTarget, aTextRun,
                                                 convertedString.BeginReading(),
                                                 aOffset + runStart, runLength,
--- a/gfx/thebes/gfxTextRun.cpp
+++ b/gfx/thebes/gfxTextRun.cpp
@@ -205,82 +205,81 @@ void
 gfxTextRun::ReleaseFontGroup()
 {
     NS_ASSERTION(!mReleasedFontGroup, "doubly released!");
     NS_RELEASE(mFontGroup);
     mReleasedFontGroup = true;
 }
 
 bool
-gfxTextRun::SetPotentialLineBreaks(uint32_t aStart, uint32_t aLength,
-                                   uint8_t *aBreakBefore)
+gfxTextRun::SetPotentialLineBreaks(Range aRange, uint8_t *aBreakBefore)
 {
-    NS_ASSERTION(aStart + aLength <= GetLength(), "Overflow");
+    NS_ASSERTION(aRange.end <= GetLength(), "Overflow");
 
     uint32_t changed = 0;
     uint32_t i;
-    CompressedGlyph *charGlyphs = mCharacterGlyphs + aStart;
-    for (i = 0; i < aLength; ++i) {
+    CompressedGlyph *charGlyphs = mCharacterGlyphs + aRange.start;
+    for (i = 0; i < aRange.Length(); ++i) {
         uint8_t canBreak = aBreakBefore[i];
         if (canBreak && !charGlyphs[i].IsClusterStart()) {
             // This can happen ... there is no guarantee that our linebreaking rules
             // align with the platform's idea of what constitutes a cluster.
             canBreak = CompressedGlyph::FLAG_BREAK_TYPE_NONE;
         }
         changed |= charGlyphs[i].SetCanBreakBefore(canBreak);
     }
     return changed != 0;
 }
 
 gfxTextRun::LigatureData
-gfxTextRun::ComputeLigatureData(uint32_t aPartStart, uint32_t aPartEnd,
-                                PropertyProvider *aProvider)
+gfxTextRun::ComputeLigatureData(Range aPartRange, PropertyProvider *aProvider)
 {
-    NS_ASSERTION(aPartStart < aPartEnd, "Computing ligature data for empty range");
-    NS_ASSERTION(aPartEnd <= GetLength(), "Character length overflow");
+    NS_ASSERTION(aPartRange.start < aPartRange.end,
+                 "Computing ligature data for empty range");
+    NS_ASSERTION(aPartRange.end <= GetLength(), "Character length overflow");
   
     LigatureData result;
     CompressedGlyph *charGlyphs = mCharacterGlyphs;
 
     uint32_t i;
-    for (i = aPartStart; !charGlyphs[i].IsLigatureGroupStart(); --i) {
+    for (i = aPartRange.start; !charGlyphs[i].IsLigatureGroupStart(); --i) {
         NS_ASSERTION(i > 0, "Ligature at the start of the run??");
     }
-    result.mLigatureStart = i;
-    for (i = aPartStart + 1; i < GetLength() && !charGlyphs[i].IsLigatureGroupStart(); ++i) {
+    result.mRange.start = i;
+    for (i = aPartRange.start + 1;
+         i < GetLength() && !charGlyphs[i].IsLigatureGroupStart(); ++i) {
     }
-    result.mLigatureEnd = i;
-
-    int32_t ligatureWidth =
-        GetAdvanceForGlyphs(result.mLigatureStart, result.mLigatureEnd);
+    result.mRange.end = i;
+
+    int32_t ligatureWidth = GetAdvanceForGlyphs(result.mRange);
     // Count the number of started clusters we have seen
     uint32_t totalClusterCount = 0;
     uint32_t partClusterIndex = 0;
     uint32_t partClusterCount = 0;
-    for (i = result.mLigatureStart; i < result.mLigatureEnd; ++i) {
+    for (i = result.mRange.start; i < result.mRange.end; ++i) {
         // Treat the first character of the ligature as the start of a
         // cluster for our purposes of allocating ligature width to its
         // characters.
-        if (i == result.mLigatureStart || charGlyphs[i].IsClusterStart()) {
+        if (i == result.mRange.start || charGlyphs[i].IsClusterStart()) {
             ++totalClusterCount;
-            if (i < aPartStart) {
+            if (i < aPartRange.start) {
                 ++partClusterIndex;
-            } else if (i < aPartEnd) {
+            } else if (i < aPartRange.end) {
                 ++partClusterCount;
             }
         }
     }
     NS_ASSERTION(totalClusterCount > 0, "Ligature involving no clusters??");
     result.mPartAdvance = partClusterIndex * (ligatureWidth / totalClusterCount);
     result.mPartWidth = partClusterCount * (ligatureWidth / totalClusterCount);
 
     // Any rounding errors are apportioned to the final part of the ligature,
     // so that measuring all parts of a ligature and summing them is equal to
     // the ligature width.
-    if (aPartEnd == result.mLigatureEnd) {
+    if (aPartRange.end == result.mRange.end) {
         gfxFloat allParts = totalClusterCount * (ligatureWidth / totalClusterCount);
         result.mPartWidth += ligatureWidth - allParts;
     }
 
     if (partClusterCount == 0) {
         // nothing to draw
         result.mClipBeforePart = result.mClipAfterPart = true;
     } else {
@@ -291,122 +290,128 @@ gfxTextRun::ComputeLigatureData(uint32_t
         result.mClipBeforePart = partClusterIndex > 0;
         // We need to clip after the part if any cluster is drawn after
         // this part.
         result.mClipAfterPart = partClusterIndex + partClusterCount < totalClusterCount;
     }
 
     if (aProvider && (mFlags & gfxTextRunFactory::TEXT_ENABLE_SPACING)) {
         gfxFont::Spacing spacing;
-        if (aPartStart == result.mLigatureStart) {
-            aProvider->GetSpacing(aPartStart, 1, &spacing);
+        if (aPartRange.start == result.mRange.start) {
+            aProvider->GetSpacing(
+                Range(aPartRange.start, aPartRange.start + 1), &spacing);
             result.mPartWidth += spacing.mBefore;
         }
-        if (aPartEnd == result.mLigatureEnd) {
-            aProvider->GetSpacing(aPartEnd - 1, 1, &spacing);
+        if (aPartRange.end == result.mRange.end) {
+            aProvider->GetSpacing(
+                Range(aPartRange.end - 1, aPartRange.end), &spacing);
             result.mPartWidth += spacing.mAfter;
         }
     }
 
     return result;
 }
 
 gfxFloat
-gfxTextRun::ComputePartialLigatureWidth(uint32_t aPartStart, uint32_t aPartEnd,
+gfxTextRun::ComputePartialLigatureWidth(Range aPartRange,
                                         PropertyProvider *aProvider)
 {
-    if (aPartStart >= aPartEnd)
+    if (aPartRange.start >= aPartRange.end)
         return 0;
-    LigatureData data = ComputeLigatureData(aPartStart, aPartEnd, aProvider);
+    LigatureData data = ComputeLigatureData(aPartRange, aProvider);
     return data.mPartWidth;
 }
 
 int32_t
-gfxTextRun::GetAdvanceForGlyphs(uint32_t aStart, uint32_t aEnd)
+gfxTextRun::GetAdvanceForGlyphs(Range aRange)
 {
     int32_t advance = 0;
-    for (auto i = aStart; i < aEnd; ++i) {
+    for (auto i = aRange.start; i < aRange.end; ++i) {
         advance += GetAdvanceForGlyph(i);
     }
     return advance;
 }
 
 static void
-GetAdjustedSpacing(gfxTextRun *aTextRun, uint32_t aStart, uint32_t aEnd,
+GetAdjustedSpacing(gfxTextRun *aTextRun, gfxTextRun::Range aRange,
                    gfxTextRun::PropertyProvider *aProvider,
                    gfxTextRun::PropertyProvider::Spacing *aSpacing)
 {
-    if (aStart >= aEnd)
+    if (aRange.start >= aRange.end)
         return;
 
-    aProvider->GetSpacing(aStart, aEnd - aStart, aSpacing);
+    aProvider->GetSpacing(aRange, aSpacing);
 
 #ifdef DEBUG
     // Check to see if we have spacing inside ligatures
 
     const gfxTextRun::CompressedGlyph *charGlyphs = aTextRun->GetCharacterGlyphs();
     uint32_t i;
 
-    for (i = aStart; i < aEnd; ++i) {
+    for (i = aRange.start; i < aRange.end; ++i) {
         if (!charGlyphs[i].IsLigatureGroupStart()) {
-            NS_ASSERTION(i == aStart || aSpacing[i - aStart].mBefore == 0,
+            NS_ASSERTION(i == aRange.start ||
+                         aSpacing[i - aRange.start].mBefore == 0,
                          "Before-spacing inside a ligature!");
-            NS_ASSERTION(i - 1 <= aStart || aSpacing[i - 1 - aStart].mAfter == 0,
+            NS_ASSERTION(i - 1 <= aRange.start ||
+                         aSpacing[i - 1 - aRange.start].mAfter == 0,
                          "After-spacing inside a ligature!");
         }
     }
 #endif
 }
 
 bool
-gfxTextRun::GetAdjustedSpacingArray(uint32_t aStart, uint32_t aEnd,
-                                    PropertyProvider *aProvider,
-                                    uint32_t aSpacingStart, uint32_t aSpacingEnd,
+gfxTextRun::GetAdjustedSpacingArray(Range aRange, PropertyProvider *aProvider,
+                                    Range aSpacingRange,
                                     nsTArray<PropertyProvider::Spacing> *aSpacing)
 {
     if (!aProvider || !(mFlags & gfxTextRunFactory::TEXT_ENABLE_SPACING))
         return false;
-    if (!aSpacing->AppendElements(aEnd - aStart))
+    if (!aSpacing->AppendElements(aRange.Length()))
         return false;
-    memset(aSpacing->Elements(), 0, sizeof(gfxFont::Spacing)*(aSpacingStart - aStart));
-    GetAdjustedSpacing(this, aSpacingStart, aSpacingEnd, aProvider,
-                       aSpacing->Elements() + aSpacingStart - aStart);
-    memset(aSpacing->Elements() + aSpacingEnd - aStart, 0, sizeof(gfxFont::Spacing)*(aEnd - aSpacingEnd));
+    auto spacingOffset = aSpacingRange.start - aRange.start;
+    memset(aSpacing->Elements(), 0, sizeof(gfxFont::Spacing) * spacingOffset);
+    GetAdjustedSpacing(this, aSpacingRange, aProvider,
+                       aSpacing->Elements() + spacingOffset);
+    memset(aSpacing->Elements() + aSpacingRange.end - aRange.start, 0,
+           sizeof(gfxFont::Spacing) * (aRange.end - aSpacingRange.end));
     return true;
 }
 
 void
-gfxTextRun::ShrinkToLigatureBoundaries(uint32_t *aStart, uint32_t *aEnd)
+gfxTextRun::ShrinkToLigatureBoundaries(Range* aRange)
 {
-    if (*aStart >= *aEnd)
+    if (aRange->start >= aRange->end)
         return;
   
     CompressedGlyph *charGlyphs = mCharacterGlyphs;
 
-    while (*aStart < *aEnd && !charGlyphs[*aStart].IsLigatureGroupStart()) {
-        ++(*aStart);
+    while (aRange->start < aRange->end &&
+           !charGlyphs[aRange->start].IsLigatureGroupStart()) {
+        ++aRange->start;
     }
-    if (*aEnd < GetLength()) {
-        while (*aEnd > *aStart && !charGlyphs[*aEnd].IsLigatureGroupStart()) {
-            --(*aEnd);
+    if (aRange->end < GetLength()) {
+        while (aRange->end > aRange->start &&
+               !charGlyphs[aRange->end].IsLigatureGroupStart()) {
+            --aRange->end;
         }
     }
 }
 
 void
-gfxTextRun::DrawGlyphs(gfxFont *aFont, uint32_t aStart, uint32_t aEnd,
-                       gfxPoint *aPt, PropertyProvider *aProvider,
-                       uint32_t aSpacingStart, uint32_t aSpacingEnd,
+gfxTextRun::DrawGlyphs(gfxFont *aFont, Range aRange, gfxPoint *aPt,
+                       PropertyProvider *aProvider, Range aSpacingRange,
                        TextRunDrawParams& aParams, uint16_t aOrientation)
 {
     AutoTArray<PropertyProvider::Spacing,200> spacingBuffer;
-    bool haveSpacing = GetAdjustedSpacingArray(aStart, aEnd, aProvider,
-        aSpacingStart, aSpacingEnd, &spacingBuffer);
+    bool haveSpacing = GetAdjustedSpacingArray(aRange, aProvider,
+                                               aSpacingRange, &spacingBuffer);
     aParams.spacing = haveSpacing ? spacingBuffer.Elements() : nullptr;
-    aFont->Draw(this, aStart, aEnd, aPt, aParams, aOrientation);
+    aFont->Draw(this, aRange.start, aRange.end, aPt, aParams, aOrientation);
 }
 
 static void
 ClipPartialLigature(const gfxTextRun* aTextRun,
                     gfxFloat *aStart, gfxFloat *aEnd,
                     gfxFloat aOrigin,
                     gfxTextRun::LigatureData *aLigature)
 {
@@ -424,26 +429,26 @@ ClipPartialLigature(const gfxTextRun* aT
             *aStart = std::max(*aStart, endEdge);
         } else {
             *aEnd = std::min(*aEnd, endEdge);
         }
     }    
 }
 
 void
-gfxTextRun::DrawPartialLigature(gfxFont *aFont, uint32_t aStart, uint32_t aEnd,
+gfxTextRun::DrawPartialLigature(gfxFont *aFont, Range aRange,
                                 gfxPoint *aPt, PropertyProvider *aProvider,
                                 TextRunDrawParams& aParams, uint16_t aOrientation)
 {
-    if (aStart >= aEnd) {
+    if (aRange.start >= aRange.end) {
         return;
     }
 
     // Draw partial ligature. We hack this by clipping the ligature.
-    LigatureData data = ComputeLigatureData(aStart, aEnd, aProvider);
+    LigatureData data = ComputeLigatureData(aRange, aProvider);
     gfxRect clipExtents = aParams.context->GetClipExtents();
     gfxFloat start, end;
     if (aParams.isVerticalRun) {
         start = clipExtents.Y() * mAppUnitsPerDevUnit;
         end = clipExtents.YMost() * mAppUnitsPerDevUnit;
         ClipPartialLigature(this, &start, &end, aPt->y, &data);
     } else {
         start = clipExtents.X() * mAppUnitsPerDevUnit;
@@ -468,35 +473,35 @@ gfxTextRun::DrawPartialLigature(gfxFont 
 
     gfxPoint pt;
     if (aParams.isVerticalRun) {
         pt = gfxPoint(aPt->x, aPt->y - aParams.direction * data.mPartAdvance);
     } else {
         pt = gfxPoint(aPt->x - aParams.direction * data.mPartAdvance, aPt->y);
     }
 
-    DrawGlyphs(aFont, data.mLigatureStart, data.mLigatureEnd, &pt,
-               aProvider, aStart, aEnd, aParams, aOrientation);
+    DrawGlyphs(aFont, data.mRange, &pt,
+               aProvider, aRange, aParams, aOrientation);
     aParams.context->Restore();
 
     if (aParams.isVerticalRun) {
         aPt->y += aParams.direction * data.mPartWidth;
     } else {
         aPt->x += aParams.direction * data.mPartWidth;
     }
 }
 
 // Returns true if a glyph run is using a font with synthetic bolding enabled,
 // or a color font (COLR/SVG/sbix/CBDT), false otherwise. This is used to
 // check whether the text run needs to be explicitly composited in order to
 // support opacity.
 static bool
-HasSyntheticBoldOrColor(gfxTextRun *aRun, uint32_t aStart, uint32_t aLength)
+HasSyntheticBoldOrColor(gfxTextRun *aRun, gfxTextRun::Range aRange)
 {
-    gfxTextRun::GlyphRunIterator iter(aRun, aStart, aLength);
+    gfxTextRun::GlyphRunIterator iter(aRun, aRange);
     while (iter.NextRun()) {
         gfxFont *font = iter.GetGlyphRun()->mFont;
         if (font) {
             if (font->IsSyntheticBold()) {
                 return true;
             }
             gfxFontEntry* fe = font->GetFontEntry();
             if (fe->TryGetSVGData(font) || fe->TryGetColorGlyphs()) {
@@ -555,22 +560,22 @@ struct BufferAlphaColor {
         mContext->Restore();
     }
 
     gfxContext *mContext;
 };
 
 void
 gfxTextRun::Draw(gfxContext *aContext, gfxPoint aPt, DrawMode aDrawMode,
-                 uint32_t aStart, uint32_t aLength,
+                 Range aRange,
                  PropertyProvider *aProvider, gfxFloat *aAdvanceWidth,
                  gfxTextContextPaint *aContextPaint,
                  gfxTextRunDrawCallbacks *aCallbacks)
 {
-    NS_ASSERTION(aStart + aLength <= GetLength(), "Substring out of range");
+    NS_ASSERTION(aRange.end <= GetLength(), "Substring out of range");
     NS_ASSERTION(aDrawMode == DrawMode::GLYPH_PATH ||
                  !(int(aDrawMode) & int(DrawMode::GLYPH_PATH)),
                  "GLYPH_PATH cannot be used with GLYPH_FILL, GLYPH_STROKE or GLYPH_STROKE_UNDERNEATH");
     NS_ASSERTION(aDrawMode == DrawMode::GLYPH_PATH || !aCallbacks,
                  "callback must not be specified unless using GLYPH_PATH");
 
     bool skipDrawing = mSkipDrawing;
     if (aDrawMode == DrawMode::GLYPH_FILL) {
@@ -581,17 +586,17 @@ gfxTextRun::Draw(gfxContext *aContext, g
     }
 
     gfxFloat direction = GetDirection();
 
     if (skipDrawing) {
         // We don't need to draw anything;
         // but if the caller wants advance width, we need to compute it here
         if (aAdvanceWidth) {
-            gfxTextRun::Metrics metrics = MeasureText(aStart, aLength,
+            gfxTextRun::Metrics metrics = MeasureText(aRange,
                                                       gfxFont::LOOSE_INK_EXTENTS,
                                                       aContext->GetDrawTarget(),
                                                       aProvider);
             *aAdvanceWidth = metrics.mAdvanceWidth * direction;
         }
 
         // return without drawing
         return;
@@ -600,20 +605,20 @@ gfxTextRun::Draw(gfxContext *aContext, g
     // synthetic bolding draws glyphs twice ==> colors with opacity won't draw
     // correctly unless first drawn without alpha
     BufferAlphaColor syntheticBoldBuffer(aContext);
     Color currentColor;
     bool needToRestore = false;
 
     if (aDrawMode == DrawMode::GLYPH_FILL &&
         HasNonOpaqueNonTransparentColor(aContext, currentColor) &&
-        HasSyntheticBoldOrColor(this, aStart, aLength)) {
+        HasSyntheticBoldOrColor(this, aRange)) {
         needToRestore = true;
         // measure text, use the bounding box
-        gfxTextRun::Metrics metrics = MeasureText(aStart, aLength,
+        gfxTextRun::Metrics metrics = MeasureText(aRange,
                                                   gfxFont::LOOSE_INK_EXTENTS,
                                                   aContext->GetDrawTarget(),
                                                   aProvider);
         metrics.mBoundingBox.MoveBy(aPt);
         syntheticBoldBuffer.PushSolidColor(metrics.mBoundingBox, currentColor,
                                            GetAppUnitsPerDevUnit());
     }
 
@@ -627,44 +632,43 @@ gfxTextRun::Draw(gfxContext *aContext, g
     params.direction = direction;
     params.drawMode = aDrawMode;
     params.callbacks = aCallbacks;
     params.runContextPaint = aContextPaint;
     params.paintSVGGlyphs = !aCallbacks || aCallbacks->mShouldPaintSVGGlyphs;
     params.dt = aContext->GetDrawTarget();
     params.fontSmoothingBGColor = aContext->GetFontSmoothingBackgroundColor();
 
-    GlyphRunIterator iter(this, aStart, aLength);
+    GlyphRunIterator iter(this, aRange);
     gfxFloat advance = 0.0;
 
     while (iter.NextRun()) {
         gfxFont *font = iter.GetGlyphRun()->mFont;
         uint32_t start = iter.GetStringStart();
         uint32_t end = iter.GetStringEnd();
-        uint32_t ligatureRunStart = start;
-        uint32_t ligatureRunEnd = end;
-        ShrinkToLigatureBoundaries(&ligatureRunStart, &ligatureRunEnd);
+        Range ligatureRange(start, end);
+        ShrinkToLigatureBoundaries(&ligatureRange);
 
         bool drawPartial = aDrawMode == DrawMode::GLYPH_FILL ||
                            (aDrawMode == DrawMode::GLYPH_PATH && aCallbacks);
         gfxPoint origPt = aPt;
 
         if (drawPartial) {
-            DrawPartialLigature(font, start, ligatureRunStart, &aPt,
-                                aProvider, params,
+            DrawPartialLigature(font, Range(start, ligatureRange.start),
+                                &aPt, aProvider, params,
                                 iter.GetGlyphRun()->mOrientation);
         }
 
-        DrawGlyphs(font, ligatureRunStart, ligatureRunEnd, &aPt,
-                   aProvider, ligatureRunStart, ligatureRunEnd, params,
+        DrawGlyphs(font, ligatureRange, &aPt,
+                   aProvider, ligatureRange, params,
                    iter.GetGlyphRun()->mOrientation);
 
         if (drawPartial) {
-            DrawPartialLigature(font, ligatureRunEnd, end, &aPt,
-                                aProvider, params,
+            DrawPartialLigature(font, Range(ligatureRange.end, end),
+                                &aPt, aProvider, params,
                                 iter.GetGlyphRun()->mOrientation);
         }
 
         if (params.isVerticalRun) {
             advance += (aPt.y - origPt.y) * params.direction;
         } else {
             advance += (aPt.x - origPt.x) * params.direction;
         }
@@ -679,94 +683,89 @@ gfxTextRun::Draw(gfxContext *aContext, g
         *aAdvanceWidth = advance;
     }
 }
 
 // This method is mostly parallel to Draw().
 void
 gfxTextRun::DrawEmphasisMarks(gfxContext *aContext, gfxTextRun* aMark,
                               gfxFloat aMarkAdvance, gfxPoint aPt,
-                              uint32_t aStart, uint32_t aLength,
-                              PropertyProvider* aProvider)
+                              Range aRange, PropertyProvider* aProvider)
 {
-    MOZ_ASSERT(aStart + aLength <= GetLength());
+    MOZ_ASSERT(aRange.end <= GetLength());
 
     EmphasisMarkDrawParams params;
     params.context = aContext;
     params.mark = aMark;
     params.advance = aMarkAdvance;
     params.direction = GetDirection();
     params.isVertical = IsVertical();
 
     gfxFloat& inlineCoord = params.isVertical ? aPt.y : aPt.x;
     gfxFloat direction = params.direction;
 
-    GlyphRunIterator iter(this, aStart, aLength);
+    GlyphRunIterator iter(this, aRange);
     while (iter.NextRun()) {
         gfxFont* font = iter.GetGlyphRun()->mFont;
         uint32_t start = iter.GetStringStart();
         uint32_t end = iter.GetStringEnd();
-        uint32_t ligatureRunStart = start;
-        uint32_t ligatureRunEnd = end;
-        ShrinkToLigatureBoundaries(&ligatureRunStart, &ligatureRunEnd);
-
-        inlineCoord += direction *
-            ComputePartialLigatureWidth(start, ligatureRunStart, aProvider);
+        Range ligatureRange(start, end);
+        ShrinkToLigatureBoundaries(&ligatureRange);
+
+        inlineCoord += direction * ComputePartialLigatureWidth(
+            Range(start, ligatureRange.start), aProvider);
 
         AutoTArray<PropertyProvider::Spacing, 200> spacingBuffer;
         bool haveSpacing = GetAdjustedSpacingArray(
-            ligatureRunStart, ligatureRunEnd, aProvider,
-            ligatureRunStart, ligatureRunEnd, &spacingBuffer);
+            ligatureRange, aProvider, ligatureRange, &spacingBuffer);
         params.spacing = haveSpacing ? spacingBuffer.Elements() : nullptr;
-        font->DrawEmphasisMarks(this, &aPt, ligatureRunStart,
-                                ligatureRunEnd - ligatureRunStart, params);
-
-        inlineCoord += direction *
-            ComputePartialLigatureWidth(ligatureRunEnd, end, aProvider);
+        font->DrawEmphasisMarks(this, &aPt, ligatureRange.start,
+                                ligatureRange.Length(), params);
+
+        inlineCoord += direction * ComputePartialLigatureWidth(
+            Range(ligatureRange.end, end), aProvider);
     }
 }
 
 void
-gfxTextRun::AccumulateMetricsForRun(gfxFont *aFont,
-                                    uint32_t aStart, uint32_t aEnd,
+gfxTextRun::AccumulateMetricsForRun(gfxFont *aFont, Range aRange,
                                     gfxFont::BoundingBoxType aBoundingBoxType,
                                     DrawTarget* aRefDrawTarget,
                                     PropertyProvider *aProvider,
-                                    uint32_t aSpacingStart, uint32_t aSpacingEnd,
+                                    Range aSpacingRange,
                                     uint16_t aOrientation,
                                     Metrics *aMetrics)
 {
     AutoTArray<PropertyProvider::Spacing,200> spacingBuffer;
-    bool haveSpacing = GetAdjustedSpacingArray(aStart, aEnd, aProvider,
-        aSpacingStart, aSpacingEnd, &spacingBuffer);
-    Metrics metrics = aFont->Measure(this, aStart, aEnd, aBoundingBoxType,
-                                     aRefDrawTarget,
+    bool haveSpacing = GetAdjustedSpacingArray(aRange, aProvider,
+                                               aSpacingRange, &spacingBuffer);
+    Metrics metrics = aFont->Measure(this, aRange.start, aRange.end,
+                                     aBoundingBoxType, aRefDrawTarget,
                                      haveSpacing ? spacingBuffer.Elements() : nullptr,
                                      aOrientation);
     aMetrics->CombineWith(metrics, IsRightToLeft());
 }
 
 void
-gfxTextRun::AccumulatePartialLigatureMetrics(gfxFont *aFont,
-    uint32_t aStart, uint32_t aEnd,
+gfxTextRun::AccumulatePartialLigatureMetrics(gfxFont *aFont, Range aRange,
     gfxFont::BoundingBoxType aBoundingBoxType, DrawTarget* aRefDrawTarget,
     PropertyProvider *aProvider, uint16_t aOrientation, Metrics *aMetrics)
 {
-    if (aStart >= aEnd)
+    if (aRange.start >= aRange.end)
         return;
 
     // Measure partial ligature. We hack this by clipping the metrics in the
     // same way we clip the drawing.
-    LigatureData data = ComputeLigatureData(aStart, aEnd, aProvider);
+    LigatureData data = ComputeLigatureData(aRange, aProvider);
 
     // First measure the complete ligature
     Metrics metrics;
-    AccumulateMetricsForRun(aFont, data.mLigatureStart, data.mLigatureEnd,
+    AccumulateMetricsForRun(aFont, data.mRange,
                             aBoundingBoxType, aRefDrawTarget,
-                            aProvider, aStart, aEnd, aOrientation, &metrics);
+                            aProvider, aRange, aOrientation, &metrics);
 
     // Clip the bounding box to the ligature part
     gfxFloat bboxLeft = metrics.mBoundingBox.X();
     gfxFloat bboxRight = metrics.mBoundingBox.XMost();
     // Where we are going to start "drawing" relative to our left baseline origin
     gfxFloat origin = IsRightToLeft() ? metrics.mAdvanceWidth - data.mPartAdvance : 0;
     ClipPartialLigature(this, &bboxLeft, &bboxRight, origin, &data);
     metrics.mBoundingBox.x = bboxLeft;
@@ -778,48 +777,49 @@ gfxTextRun::AccumulatePartialLigatureMet
         IsRightToLeft() ? metrics.mAdvanceWidth - (data.mPartAdvance + data.mPartWidth)
             : data.mPartAdvance;    
     metrics.mAdvanceWidth = data.mPartWidth;
 
     aMetrics->CombineWith(metrics, IsRightToLeft());
 }
 
 gfxTextRun::Metrics
-gfxTextRun::MeasureText(uint32_t aStart, uint32_t aLength,
+gfxTextRun::MeasureText(Range aRange,
                         gfxFont::BoundingBoxType aBoundingBoxType,
                         DrawTarget* aRefDrawTarget,
                         PropertyProvider *aProvider)
 {
-    NS_ASSERTION(aStart + aLength <= GetLength(), "Substring out of range");
+    NS_ASSERTION(aRange.end <= GetLength(), "Substring out of range");
 
     Metrics accumulatedMetrics;
-    GlyphRunIterator iter(this, aStart, aLength);
+    GlyphRunIterator iter(this, aRange);
     while (iter.NextRun()) {
         gfxFont *font = iter.GetGlyphRun()->mFont;
         uint32_t start = iter.GetStringStart();
         uint32_t end = iter.GetStringEnd();
-        uint32_t ligatureRunStart = start;
-        uint32_t ligatureRunEnd = end;
-        ShrinkToLigatureBoundaries(&ligatureRunStart, &ligatureRunEnd);
-
-        AccumulatePartialLigatureMetrics(font, start, ligatureRunStart,
+        Range ligatureRange(start, end);
+        ShrinkToLigatureBoundaries(&ligatureRange);
+
+        AccumulatePartialLigatureMetrics(
+            font, Range(start, ligatureRange.start),
             aBoundingBoxType, aRefDrawTarget, aProvider,
             iter.GetGlyphRun()->mOrientation, &accumulatedMetrics);
 
         // XXX This sucks. We have to get glyph extents just so we can detect
         // glyphs outside the font box, even when aBoundingBoxType is LOOSE,
         // even though in almost all cases we could get correct results just
         // by getting some ascent/descent from the font and using our stored
         // advance widths.
         AccumulateMetricsForRun(font,
-            ligatureRunStart, ligatureRunEnd, aBoundingBoxType,
-            aRefDrawTarget, aProvider, ligatureRunStart, ligatureRunEnd,
+            ligatureRange, aBoundingBoxType,
+            aRefDrawTarget, aProvider, ligatureRange,
             iter.GetGlyphRun()->mOrientation, &accumulatedMetrics);
 
-        AccumulatePartialLigatureMetrics(font, ligatureRunEnd, end,
+        AccumulatePartialLigatureMetrics(
+            font, Range(ligatureRange.end, end),
             aBoundingBoxType, aRefDrawTarget, aProvider,
             iter.GetGlyphRun()->mOrientation, &accumulatedMetrics);
     }
 
     return accumulatedMetrics;
 }
 
 #define MEASUREMENT_BUFFER_SIZE 100
@@ -837,76 +837,72 @@ gfxTextRun::BreakAndMeasureText(uint32_t
                                 uint32_t *aLastBreak,
                                 bool aCanWordWrap,
                                 gfxBreakPriority *aBreakPriority)
 {
     aMaxLength = std::min(aMaxLength, GetLength() - aStart);
 
     NS_ASSERTION(aStart + aMaxLength <= GetLength(), "Substring out of range");
 
-    uint32_t bufferStart = aStart;
-    uint32_t bufferLength = std::min<uint32_t>(aMaxLength, MEASUREMENT_BUFFER_SIZE);
+    Range bufferRange(aStart, aStart +
+        std::min<uint32_t>(aMaxLength, MEASUREMENT_BUFFER_SIZE));
     PropertyProvider::Spacing spacingBuffer[MEASUREMENT_BUFFER_SIZE];
     bool haveSpacing = aProvider && (mFlags & gfxTextRunFactory::TEXT_ENABLE_SPACING) != 0;
     if (haveSpacing) {
-        GetAdjustedSpacing(this, bufferStart, bufferStart + bufferLength, aProvider,
-                           spacingBuffer);
+        GetAdjustedSpacing(this, bufferRange, aProvider, spacingBuffer);
     }
     bool hyphenBuffer[MEASUREMENT_BUFFER_SIZE];
     bool haveHyphenation = aProvider &&
         (aProvider->GetHyphensOption() == NS_STYLE_HYPHENS_AUTO ||
          (aProvider->GetHyphensOption() == NS_STYLE_HYPHENS_MANUAL &&
           (mFlags & gfxTextRunFactory::TEXT_ENABLE_HYPHEN_BREAKS) != 0));
     if (haveHyphenation) {
-        aProvider->GetHyphenationBreaks(bufferStart, bufferLength,
-                                        hyphenBuffer);
+        aProvider->GetHyphenationBreaks(bufferRange, hyphenBuffer);
     }
 
     gfxFloat width = 0;
     gfxFloat advance = 0;
     // The number of space characters that can be trimmed
     uint32_t trimmableChars = 0;
     // The amount of space removed by ignoring trimmableChars
     gfxFloat trimmableAdvance = 0;
     int32_t lastBreak = -1;
     int32_t lastBreakTrimmableChars = -1;
     gfxFloat lastBreakTrimmableAdvance = -1;
     bool aborted = false;
     uint32_t end = aStart + aMaxLength;
     bool lastBreakUsedHyphenation = false;
 
-    uint32_t ligatureRunStart = aStart;
-    uint32_t ligatureRunEnd = end;
-    ShrinkToLigatureBoundaries(&ligatureRunStart, &ligatureRunEnd);
+    Range ligatureRange(aStart, end);
+    ShrinkToLigatureBoundaries(&ligatureRange);
 
     uint32_t i;
     for (i = aStart; i < end; ++i) {
-        if (i >= bufferStart + bufferLength) {
+        if (i >= bufferRange.end) {
             // Fetch more spacing and hyphenation data
-            bufferStart = i;
-            bufferLength = std::min(aStart + aMaxLength, i + MEASUREMENT_BUFFER_SIZE) - i;
+            bufferRange.start = i;
+            bufferRange.end = std::min(aStart + aMaxLength,
+                                       i + MEASUREMENT_BUFFER_SIZE);
             if (haveSpacing) {
-                GetAdjustedSpacing(this, bufferStart, bufferStart + bufferLength, aProvider,
-                                   spacingBuffer);
+                GetAdjustedSpacing(this, bufferRange, aProvider, spacingBuffer);
             }
             if (haveHyphenation) {
-                aProvider->GetHyphenationBreaks(bufferStart, bufferLength,
-                                                hyphenBuffer);
+                aProvider->GetHyphenationBreaks(bufferRange, hyphenBuffer);
             }
         }
 
         // There can't be a word-wrap break opportunity at the beginning of the
         // line: if the width is too small for even one character to fit, it 
         // could be the first and last break opportunity on the line, and that
         // would trigger an infinite loop.
         if (aSuppressBreak != eSuppressAllBreaks &&
             (aSuppressBreak != eSuppressInitialBreak || i > aStart)) {
             bool atNaturalBreak = mCharacterGlyphs[i].CanBreakBefore() == 1;
-            bool atHyphenationBreak =
-                !atNaturalBreak && haveHyphenation && hyphenBuffer[i - bufferStart];
+            bool atHyphenationBreak = !atNaturalBreak &&
+                haveHyphenation && hyphenBuffer[i - bufferRange.start];
             bool atBreak = atNaturalBreak || atHyphenationBreak;
             bool wordWrapping =
                 aCanWordWrap && mCharacterGlyphs[i].IsClusterStart() &&
                 *aBreakPriority <= gfxBreakPriority::eWordWrapBreak;
 
             if (atBreak || wordWrapping) {
                 gfxFloat hyphenatedAdvance = advance;
                 if (atHyphenationBreak) {
@@ -929,24 +925,26 @@ gfxTextRun::BreakAndMeasureText(uint32_t
                     // No more text fits. Abort
                     aborted = true;
                     break;
                 }
             }
         }
         
         gfxFloat charAdvance;
-        if (i >= ligatureRunStart && i < ligatureRunEnd) {
-            charAdvance = GetAdvanceForGlyphs(i, i + 1);
+        if (i >= ligatureRange.start && i < ligatureRange.end) {
+            charAdvance = GetAdvanceForGlyphs(Range(i, i + 1));
             if (haveSpacing) {
-                PropertyProvider::Spacing *space = &spacingBuffer[i - bufferStart];
+                PropertyProvider::Spacing *space =
+                    &spacingBuffer[i - bufferRange.start];
                 charAdvance += space->mBefore + space->mAfter;
             }
         } else {
-            charAdvance = ComputePartialLigatureWidth(i, i + 1, aProvider);
+            charAdvance =
+                ComputePartialLigatureWidth(Range(i, i + 1), aProvider);
         }
         
         advance += charAdvance;
         if (aTrimWhitespace) {
             if (mCharacterGlyphs[i].CharIsSpace()) {
                 ++trimmableChars;
                 trimmableAdvance += charAdvance;
             } else {
@@ -973,23 +971,23 @@ gfxTextRun::BreakAndMeasureText(uint32_t
         trimmableChars = lastBreakTrimmableChars;
         trimmableAdvance = lastBreakTrimmableAdvance;
         usedHyphenation = lastBreakUsedHyphenation;
     } else {
         charsFit = aMaxLength;
     }
 
     if (aMetrics) {
-        *aMetrics = MeasureText(aStart, charsFit, aBoundingBoxType,
+        auto end = aStart + charsFit;
+        *aMetrics = MeasureText(Range(aStart, end), aBoundingBoxType,
                                 aRefDrawTarget, aProvider);
         if (trimmableChars) {
             Metrics trimMetrics =
-                MeasureText(aStart + charsFit - trimmableChars,
-                            trimmableChars, aBoundingBoxType,
-                            aRefDrawTarget, aProvider);
+                MeasureText(Range(end - trimmableChars, end),
+                            aBoundingBoxType, aRefDrawTarget, aProvider);
             aMetrics->mAdvanceWidth -= trimMetrics.mAdvanceWidth;
         }
     }
     if (aTrimWhitespace) {
         *aTrimWhitespace = trimmableAdvance;
     }
     if (aUsedHyphenation) {
         *aUsedHyphenation = usedHyphenation;
@@ -1001,57 +999,58 @@ gfxTextRun::BreakAndMeasureText(uint32_t
             *aLastBreak = lastBreak - aStart;
         }
     }
 
     return charsFit;
 }
 
 gfxFloat
-gfxTextRun::GetAdvanceWidth(uint32_t aStart, uint32_t aLength,
-                            PropertyProvider *aProvider,
+gfxTextRun::GetAdvanceWidth(Range aRange, PropertyProvider *aProvider,
                             PropertyProvider::Spacing* aSpacing)
 {
-    NS_ASSERTION(aStart + aLength <= GetLength(), "Substring out of range");
-
-    uint32_t ligatureRunStart = aStart;
-    uint32_t ligatureRunEnd = aStart + aLength;
-    ShrinkToLigatureBoundaries(&ligatureRunStart, &ligatureRunEnd);
-
-    gfxFloat result = ComputePartialLigatureWidth(aStart, ligatureRunStart, aProvider) +
-                      ComputePartialLigatureWidth(ligatureRunEnd, aStart + aLength, aProvider);
+    NS_ASSERTION(aRange.end <= GetLength(), "Substring out of range");
+
+    Range ligatureRange = aRange;
+    ShrinkToLigatureBoundaries(&ligatureRange);
+
+    gfxFloat result =
+        ComputePartialLigatureWidth(Range(aRange.start, ligatureRange.start),
+                                    aProvider) +
+        ComputePartialLigatureWidth(Range(ligatureRange.end, aRange.end),
+                                    aProvider);
 
     if (aSpacing) {
         aSpacing->mBefore = aSpacing->mAfter = 0;
     }
 
     // Account for all remaining spacing here. This is more efficient than
     // processing it along with the glyphs.
     if (aProvider && (mFlags & gfxTextRunFactory::TEXT_ENABLE_SPACING)) {
         uint32_t i;
         AutoTArray<PropertyProvider::Spacing,200> spacingBuffer;
-        if (spacingBuffer.AppendElements(aLength)) {
-            GetAdjustedSpacing(this, ligatureRunStart, ligatureRunEnd, aProvider,
+        if (spacingBuffer.AppendElements(aRange.Length())) {
+            GetAdjustedSpacing(this, ligatureRange, aProvider,
                                spacingBuffer.Elements());
-            for (i = 0; i < ligatureRunEnd - ligatureRunStart; ++i) {
+            for (i = 0; i < ligatureRange.Length(); ++i) {
                 PropertyProvider::Spacing *space = &spacingBuffer[i];
                 result += space->mBefore + space->mAfter;
             }
             if (aSpacing) {
                 aSpacing->mBefore = spacingBuffer[0].mBefore;
                 aSpacing->mAfter = spacingBuffer.LastElement().mAfter;
             }
         }
     }
 
-    return result + GetAdvanceForGlyphs(ligatureRunStart, ligatureRunEnd);
+    return result + GetAdvanceForGlyphs(ligatureRange);
 }
 
 bool
-gfxTextRun::SetLineBreaks(uint32_t aStart, uint32_t aLength,
+gfxTextRun::SetLineBreaks(Range aRange,
                           bool aLineBreakBefore, bool aLineBreakAfter,
                           gfxFloat *aAdvanceWidthDelta)
 {
     // Do nothing because our shaping does not currently take linebreaks into
     // account. There is no change in advance width.
     if (aAdvanceWidthDelta) {
         *aAdvanceWidthDelta = 0;
     }
@@ -1239,57 +1238,57 @@ gfxTextRun::CopyGlyphDataFrom(gfxShapedW
         }
     } else {
         memcpy(charGlyphs + aOffset, wordGlyphs,
                wordLen * sizeof(CompressedGlyph));
     }
 }
 
 void
-gfxTextRun::CopyGlyphDataFrom(gfxTextRun *aSource, uint32_t aStart,
-                              uint32_t aLength, uint32_t aDest)
+gfxTextRun::CopyGlyphDataFrom(gfxTextRun *aSource, Range aRange, uint32_t aDest)
 {
-    NS_ASSERTION(aStart + aLength <= aSource->GetLength(),
+    NS_ASSERTION(aRange.end <= aSource->GetLength(),
                  "Source substring out of range");
-    NS_ASSERTION(aDest + aLength <= GetLength(),
+    NS_ASSERTION(aDest + aRange.Length() <= GetLength(),
                  "Destination substring out of range");
 
     if (aSource->mSkipDrawing) {
         mSkipDrawing = true;
     }
 
     // Copy base glyph data, and DetailedGlyph data where present
-    const CompressedGlyph *srcGlyphs = aSource->mCharacterGlyphs + aStart;
+    const CompressedGlyph *srcGlyphs = aSource->mCharacterGlyphs + aRange.start;
     CompressedGlyph *dstGlyphs = mCharacterGlyphs + aDest;
-    for (uint32_t i = 0; i < aLength; ++i) {
+    for (uint32_t i = 0; i < aRange.Length(); ++i) {
         CompressedGlyph g = srcGlyphs[i];
         g.SetCanBreakBefore(!g.IsClusterStart() ?
             CompressedGlyph::FLAG_BREAK_TYPE_NONE :
             dstGlyphs[i].CanBreakBefore());
         if (!g.IsSimpleGlyph()) {
             uint32_t count = g.GetGlyphCount();
             if (count > 0) {
                 DetailedGlyph *dst = AllocateDetailedGlyphs(i + aDest, count);
                 if (dst) {
-                    DetailedGlyph *src = aSource->GetDetailedGlyphs(i + aStart);
+                    DetailedGlyph *src =
+                        aSource->GetDetailedGlyphs(i + aRange.start);
                     if (src) {
                         ::memcpy(dst, src, count * sizeof(DetailedGlyph));
                     } else {
                         g.SetMissing(0);
                     }
                 } else {
                     g.SetMissing(0);
                 }
             }
         }
         dstGlyphs[i] = g;
     }
 
     // Copy glyph runs
-    GlyphRunIterator iter(aSource, aStart, aLength);
+    GlyphRunIterator iter(aSource, aRange);
 #ifdef DEBUG
     GlyphRun *prevRun = nullptr;
 #endif
     while (iter.NextRun()) {
         gfxFont *font = iter.GetGlyphRun()->mFont;
         NS_ASSERTION(!prevRun || prevRun->mFont != iter.GetGlyphRun()->mFont ||
                      prevRun->mMatchType != iter.GetGlyphRun()->mMatchType ||
                      prevRun->mOrientation != iter.GetGlyphRun()->mOrientation,
@@ -1310,17 +1309,17 @@ gfxTextRun::CopyGlyphDataFrom(gfxTextRun
         // but it's the best we can do for now if the specified font only covered the
         // initial base character and not its applied marks.
         NS_WARN_IF_FALSE(aSource->IsClusterStart(start),
                          "Started font run in the middle of a cluster");
         NS_WARN_IF_FALSE(end == aSource->GetLength() || aSource->IsClusterStart(end),
                          "Ended font run in the middle of a cluster");
 
         nsresult rv = AddGlyphRun(font, iter.GetGlyphRun()->mMatchType,
-                                  start - aStart + aDest, false,
+                                  start - aRange.start + aDest, false,
                                   iter.GetGlyphRun()->mOrientation);
         if (NS_FAILED(rv))
             return;
     }
 }
 
 void
 gfxTextRun::ClearGlyphsAndCharacters()
@@ -1490,42 +1489,42 @@ gfxTextRun::ClusterIterator::NextCluster
             return true;
         }
     }
 
     mCurrentChar = uint32_t(-1);
     return false;
 }
 
-uint32_t
-gfxTextRun::ClusterIterator::ClusterLength() const
+gfxTextRun::Range
+gfxTextRun::ClusterIterator::ClusterRange() const
 {
     if (mCurrentChar == uint32_t(-1)) {
-        return 0;
+        return Range(0, 0);
     }
 
     uint32_t i = mCurrentChar,
              len = mTextRun->GetLength();
     while (++i < len) {
         if (mTextRun->IsClusterStart(i)) {
             break;
         }
     }
 
-    return i - mCurrentChar;
+    return Range(mCurrentChar, i);
 }
 
 gfxFloat
 gfxTextRun::ClusterIterator::ClusterAdvance(PropertyProvider *aProvider) const
 {
     if (mCurrentChar == uint32_t(-1)) {
         return 0;
     }
 
-    return mTextRun->GetAdvanceWidth(mCurrentChar, ClusterLength(), aProvider);
+    return mTextRun->GetAdvanceWidth(ClusterRange(), aProvider);
 }
 
 size_t
 gfxTextRun::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf)
 {
     // The second arg is how much gfxTextRun::AllocateStorage would have
     // allocated.
     size_t total = mGlyphRuns.ShallowSizeOfExcludingThis(aMallocSizeOf);
--- a/gfx/thebes/gfxTextRun.h
+++ b/gfx/thebes/gfxTextRun.h
@@ -129,53 +129,64 @@ public:
         NS_ASSERTION(aPos < GetLength(), "aPos out of range");
         return mCharacterGlyphs[aPos].CharIsNewline();
     }
     bool CharMayHaveEmphasisMark(uint32_t aPos) const {
         NS_ASSERTION(aPos < GetLength(), "aPos out of range");
         return mCharacterGlyphs[aPos].CharMayHaveEmphasisMark();
     }
 
-    // All uint32_t aStart, uint32_t aLength ranges below are restricted to
-    // grapheme cluster boundaries! All offsets are in terms of the string
-    // passed into MakeTextRun.
-    
+    // All offsets are in terms of the string passed into MakeTextRun.
+
+    // Describe range [start, end) of a text run. The range is
+    // restricted to grapheme cluster boundaries.
+    struct Range
+    {
+        uint32_t start;
+        uint32_t end;
+        uint32_t Length() const { return end - start; }
+
+        Range() : start(0), end(0) {}
+        Range(uint32_t aStart, uint32_t aEnd)
+            : start(aStart), end(aEnd) {}
+        explicit Range(gfxTextRun* aTextRun)
+            : start(0), end(aTextRun->GetLength()) {}
+    };
+
     // All coordinates are in layout/app units
 
     /**
      * Set the potential linebreaks for a substring of the textrun. These are
      * the "allow break before" points. Initially, there are no potential
      * linebreaks.
      * 
      * This can change glyphs and/or geometry! Some textruns' shapes
      * depend on potential line breaks (e.g., title-case-converting textruns).
      * This function is virtual so that those textruns can reshape themselves.
      * 
      * @return true if this changed the linebreaks, false if the new line
      * breaks are the same as the old
      */
-    virtual bool SetPotentialLineBreaks(uint32_t aStart, uint32_t aLength,
-                                        uint8_t *aBreakBefore);
+    virtual bool SetPotentialLineBreaks(Range aRange, uint8_t *aBreakBefore);
 
     /**
      * Layout provides PropertyProvider objects. These allow detection of
      * potential line break points and computation of spacing. We pass the data
      * this way to allow lazy data acquisition; for example BreakAndMeasureText
      * will want to only ask for properties of text it's actually looking at.
      * 
      * NOTE that requested spacing may not actually be applied, if the textrun
      * is unable to apply it in some context. Exception: spacing around a
      * whitespace character MUST always be applied.
      */
     class PropertyProvider {
     public:
         // Detect hyphenation break opportunities in the given range; breaks
         // not at cluster boundaries will be ignored.
-        virtual void GetHyphenationBreaks(uint32_t aStart, uint32_t aLength,
-                                          bool *aBreakBefore) = 0;
+        virtual void GetHyphenationBreaks(Range aRange, bool *aBreakBefore) = 0;
 
         // Returns the provider's hyphenation setting, so callers can decide
         // whether it is necessary to call GetHyphenationBreaks.
         // Result is an NS_STYLE_HYPHENS_* value.
         virtual int8_t GetHyphensOption() = 0;
 
         // Returns the extra width that will be consumed by a hyphen. This should
         // be constant for a given textrun.
@@ -184,18 +195,17 @@ public:
         typedef gfxFont::Spacing Spacing;
 
         /**
          * Get the spacing around the indicated characters. Spacing must be zero
          * inside clusters. In other words, if character i is not
          * CLUSTER_START, then character i-1 must have zero after-spacing and
          * character i must have zero before-spacing.
          */
-        virtual void GetSpacing(uint32_t aStart, uint32_t aLength,
-                                Spacing *aSpacing) = 0;
+        virtual void GetSpacing(Range aRange, Spacing *aSpacing) = 0;
 
         // Returns a gfxContext that can be used to measure the hyphen glyph.
         // Only called if the hyphen width is requested.
         virtual already_AddRefed<DrawTarget> GetDrawTarget() = 0;
 
         // Return the appUnitsPerDevUnit value to be used when measuring.
         // Only called if the hyphen width is requested.
         virtual uint32_t GetAppUnitsPerDevUnit() = 0;
@@ -208,98 +218,95 @@ public:
         void Reset();
 
         bool NextCluster();
 
         uint32_t Position() const {
             return mCurrentChar;
         }
 
-        uint32_t ClusterLength() const;
+        Range ClusterRange() const;
 
         gfxFloat ClusterAdvance(PropertyProvider *aProvider) const;
 
     private:
         gfxTextRun *mTextRun;
         uint32_t    mCurrentChar;
     };
 
     /**
      * Draws a substring. Uses only GetSpacing from aBreakProvider.
      * The provided point is the baseline origin on the left of the string
      * for LTR, on the right of the string for RTL.
      * @param aAdvanceWidth if non-null, the advance width of the substring
      * is returned here.
      * 
      * Drawing should respect advance widths in the sense that for LTR runs,
-     * Draw(ctx, pt, offset1, length1, dirty, &provider, &advance) followed by
-     * Draw(ctx, gfxPoint(pt.x + advance, pt.y), offset1 + length1, length2,
+     * Draw(ctx, pt, Range(start, middle), dirty, &provider, &advance) followed by
+     * Draw(ctx, gfxPoint(pt.x + advance, pt.y), Range(middle, end),
      *      dirty, &provider, nullptr) should have the same effect as
-     * Draw(ctx, pt, offset1, length1+length2, dirty, &provider, nullptr).
+     * Draw(ctx, pt, Range(start, end), dirty, &provider, nullptr).
      * For RTL runs the rule is:
-     * Draw(ctx, pt, offset1 + length1, length2, dirty, &provider, &advance) followed by
-     * Draw(ctx, gfxPoint(pt.x + advance, pt.y), offset1, length1,
+     * Draw(ctx, pt, Range(middle, end), dirty, &provider, &advance) followed by
+     * Draw(ctx, gfxPoint(pt.x + advance, pt.y), Range(start, middle),
      *      dirty, &provider, nullptr) should have the same effect as
-     * Draw(ctx, pt, offset1, length1+length2, dirty, &provider, nullptr).
+     * Draw(ctx, pt, Range(start, end), dirty, &provider, nullptr).
      * 
      * Glyphs should be drawn in logical content order, which can be significant
      * if they overlap (perhaps due to negative spacing).
      */
     void Draw(gfxContext *aContext, gfxPoint aPt,
-              DrawMode aDrawMode,
-              uint32_t aStart, uint32_t aLength,
+              DrawMode aDrawMode, Range aRange,
               PropertyProvider *aProvider,
               gfxFloat *aAdvanceWidth, gfxTextContextPaint *aContextPaint,
               gfxTextRunDrawCallbacks *aCallbacks = nullptr);
 
     /**
      * Draws the emphasis marks for this text run. Uses only GetSpacing
      * from aProvider. The provided point is the baseline origin of the
      * line of emphasis marks.
      */
     void DrawEmphasisMarks(gfxContext* aContext, gfxTextRun* aMark,
                            gfxFloat aMarkAdvance, gfxPoint aPt,
-                           uint32_t aStart, uint32_t aLength,
-                           PropertyProvider* aProvider);
+                           Range aRange, PropertyProvider* aProvider);
 
     /**
      * Computes the ReflowMetrics for a substring.
      * Uses GetSpacing from aBreakProvider.
      * @param aBoundingBoxType which kind of bounding box (loose/tight)
      */
-    Metrics MeasureText(uint32_t aStart, uint32_t aLength,
+    Metrics MeasureText(Range aRange,
                         gfxFont::BoundingBoxType aBoundingBoxType,
                         DrawTarget* aDrawTargetForTightBoundingBox,
                         PropertyProvider* aProvider);
 
     Metrics MeasureText(gfxFont::BoundingBoxType aBoundingBoxType,
                         DrawTarget* aDrawTargetForTightBoundingBox,
                         PropertyProvider* aProvider = nullptr) {
-        return MeasureText(0, GetLength(), aBoundingBoxType,
+        return MeasureText(Range(this), aBoundingBoxType,
                            aDrawTargetForTightBoundingBox, aProvider);
     }
 
     /**
      * Computes just the advance width for a substring.
      * Uses GetSpacing from aBreakProvider.
      * If aSpacing is not null, the spacing attached before and after
      * the substring would be returned in it. NOTE: the spacing is
      * included in the advance width.
      */
-    gfxFloat GetAdvanceWidth(uint32_t aStart, uint32_t aLength,
-                             PropertyProvider *aProvider,
+    gfxFloat GetAdvanceWidth(Range aRange, PropertyProvider *aProvider,
                              PropertyProvider::Spacing* aSpacing = nullptr);
 
     gfxFloat GetAdvanceWidth() {
-        return GetAdvanceWidth(0, GetLength(), nullptr);
+        return GetAdvanceWidth(Range(this), nullptr);
     }
 
     /**
      * Clear all stored line breaks for the given range (both before and after),
-     * and then set the line-break state before aStart to aBreakBefore and
+     * and then set the line-break state before aRange.start to aBreakBefore and
      * after the last cluster to aBreakAfter.
      * 
      * We require that before and after line breaks be consistent. For clusters
      * i and i+1, we require that if there is a break after cluster i, a break
      * will be specified before cluster i+1. This may be temporarily violated
      * (e.g. after reflowing line L and before reflowing line L+1); to handle
      * these temporary violations, we say that there is a break betwen i and i+1
      * if a break is specified after i OR a break is specified before i+1.
@@ -314,19 +321,19 @@ public:
      * 
      * We return true if glyphs or geometry changed, false otherwise. This
      * function is virtual so that gfxTextRun subclasses can reshape
      * properly.
      * 
      * @param aAdvanceWidthDelta if non-null, returns the change in advance
      * width of the given range.
      */
-    virtual bool SetLineBreaks(uint32_t aStart, uint32_t aLength,
-                                 bool aLineBreakBefore, bool aLineBreakAfter,
-                                 gfxFloat* aAdvanceWidthDelta);
+    virtual bool SetLineBreaks(Range aRange,
+                               bool aLineBreakBefore, bool aLineBreakAfter,
+                               gfxFloat* aAdvanceWidthDelta);
 
     enum SuppressBreak {
       eNoSuppressBreak,
       // Measure the range of text as if there is no break before it.
       eSuppressInitialBreak,
       // Measure the range of text as if it contains no break
       eSuppressAllBreaks
     };
@@ -429,19 +436,21 @@ public:
         RefPtr<gfxFont> mFont;   // never null
         uint32_t          mCharacterOffset; // into original UTF16 string
         uint8_t           mMatchType;
         uint16_t          mOrientation; // gfxTextRunFactory::TEXT_ORIENT_* value
     };
 
     class GlyphRunIterator {
     public:
-        GlyphRunIterator(gfxTextRun *aTextRun, uint32_t aStart, uint32_t aLength)
-          : mTextRun(aTextRun), mStartOffset(aStart), mEndOffset(aStart + aLength) {
-            mNextIndex = mTextRun->FindFirstGlyphRunContaining(aStart);
+        GlyphRunIterator(gfxTextRun *aTextRun, Range aRange)
+          : mTextRun(aTextRun)
+          , mStartOffset(aRange.start)
+          , mEndOffset(aRange.end) {
+            mNextIndex = mTextRun->FindFirstGlyphRunContaining(aRange.start);
         }
         bool NextRun();
         GlyphRun *GetGlyphRun() { return mGlyphRun; }
         uint32_t GetStringStart() { return mStringStart; }
         uint32_t GetStringEnd() { return mStringEnd; }
     private:
         gfxTextRun *mTextRun;
         GlyphRun   *mGlyphRun;
@@ -552,33 +561,31 @@ public:
     // Returns mGlyphRuns.Length() when aOffset is mCharacterCount.
     uint32_t FindFirstGlyphRunContaining(uint32_t aOffset);
 
     // Copy glyph data from a ShapedWord into this textrun.
     void CopyGlyphDataFrom(gfxShapedWord *aSource, uint32_t aStart);
 
     // Copy glyph data for a range of characters from aSource to this
     // textrun.
-    void CopyGlyphDataFrom(gfxTextRun *aSource, uint32_t aStart,
-                           uint32_t aLength, uint32_t aDest);
+    void CopyGlyphDataFrom(gfxTextRun *aSource, Range aRange, uint32_t aDest);
 
     nsExpirationState *GetExpirationState() { return &mExpirationState; }
 
     // Tell the textrun to release its reference to its creating gfxFontGroup
     // immediately, rather than on destruction. This is used for textruns
     // that are actually owned by a gfxFontGroup, so that they don't keep it
     // permanently alive due to a circular reference. (The caller of this is
     // taking responsibility for ensuring the textrun will not outlive its
     // mFontGroup.)
     void ReleaseFontGroup();
 
     struct LigatureData {
-        // textrun offsets of the start and end of the containing ligature
-        uint32_t mLigatureStart;
-        uint32_t mLigatureEnd;
+        // textrun range of the containing ligature
+        Range mRange;
         // appunits advance to the start of the ligature part within the ligature;
         // never includes any spacing
         gfxFloat mPartAdvance;
         // appunits width of the ligature part; includes before-spacing
         // when the part is at the start of the ligature, and after-spacing
         // when the part is as the end of the ligature
         gfxFloat mPartWidth;
         
@@ -664,71 +671,69 @@ protected:
     // Pointer to the array of CompressedGlyph records; must be initialized
     // when the object is constructed.
     CompressedGlyph *mCharacterGlyphs;
 
 private:
     // **** general helpers **** 
 
     // Get the total advance for a range of glyphs.
-    int32_t GetAdvanceForGlyphs(uint32_t aStart, uint32_t aEnd);
+    int32_t GetAdvanceForGlyphs(Range aRange);
 
     // Spacing for characters outside the range aSpacingStart/aSpacingEnd
     // is assumed to be zero; such characters are not passed to aProvider.
     // This is useful to protect aProvider from being passed character indices
     // it is not currently able to handle.
-    bool GetAdjustedSpacingArray(uint32_t aStart, uint32_t aEnd,
-                                   PropertyProvider *aProvider,
-                                   uint32_t aSpacingStart, uint32_t aSpacingEnd,
-                                   nsTArray<PropertyProvider::Spacing> *aSpacing);
+    bool GetAdjustedSpacingArray(Range aRange, PropertyProvider *aProvider,
+                                 Range aSpacingRange,
+                                 nsTArray<PropertyProvider::Spacing> *aSpacing);
 
     CompressedGlyph& EnsureComplexGlyph(uint32_t aIndex)
     {
         gfxShapedText::EnsureComplexGlyph(aIndex, mCharacterGlyphs[aIndex]);
         return mCharacterGlyphs[aIndex];
     }
 
     //  **** ligature helpers ****
     // (Platforms do the actual ligaturization, but we need to do a bunch of stuff
     // to handle requests that begin or end inside a ligature)
 
     // if aProvider is null then mBeforeSpacing and mAfterSpacing are set to zero
-    LigatureData ComputeLigatureData(uint32_t aPartStart, uint32_t aPartEnd,
+    LigatureData ComputeLigatureData(Range aPartRange,
                                      PropertyProvider *aProvider);
-    gfxFloat ComputePartialLigatureWidth(uint32_t aPartStart, uint32_t aPartEnd,
+    gfxFloat ComputePartialLigatureWidth(Range aPartRange,
                                          PropertyProvider *aProvider);
-    void DrawPartialLigature(gfxFont *aFont, uint32_t aStart, uint32_t aEnd,
+    void DrawPartialLigature(gfxFont *aFont, Range aRange,
                              gfxPoint *aPt, PropertyProvider *aProvider,
                              TextRunDrawParams& aParams, uint16_t aOrientation);
-    // Advance aStart to the start of the nearest ligature; back up aEnd
-    // to the nearest ligature end; may result in *aStart == *aEnd
-    void ShrinkToLigatureBoundaries(uint32_t *aStart, uint32_t *aEnd);
+    // Advance aRange.start to the start of the nearest ligature, back
+    // up aRange.end to the nearest ligature end; may result in
+    // aRange->start == aRange->end.
+    void ShrinkToLigatureBoundaries(Range* aRange);
     // result in appunits
-    gfxFloat GetPartialLigatureWidth(uint32_t aStart, uint32_t aEnd, PropertyProvider *aProvider);
-    void AccumulatePartialLigatureMetrics(gfxFont *aFont,
-                                          uint32_t aStart, uint32_t aEnd,
+    gfxFloat GetPartialLigatureWidth(Range aRange, PropertyProvider *aProvider);
+    void AccumulatePartialLigatureMetrics(gfxFont *aFont, Range aRange,
                                           gfxFont::BoundingBoxType aBoundingBoxType,
                                           DrawTarget* aRefDrawTarget,
                                           PropertyProvider *aProvider,
                                           uint16_t aOrientation,
                                           Metrics *aMetrics);
 
     // **** measurement helper ****
-    void AccumulateMetricsForRun(gfxFont *aFont, uint32_t aStart, uint32_t aEnd,
+    void AccumulateMetricsForRun(gfxFont *aFont, Range aRange,
                                  gfxFont::BoundingBoxType aBoundingBoxType,
                                  DrawTarget* aRefDrawTarget,
                                  PropertyProvider *aProvider,
-                                 uint32_t aSpacingStart, uint32_t aSpacingEnd,
+                                 Range aSpacingRange,
                                  uint16_t aOrientation,
                                  Metrics *aMetrics);
 
     // **** drawing helper ****
-    void DrawGlyphs(gfxFont *aFont, uint32_t aStart, uint32_t aEnd,
-                    gfxPoint *aPt, PropertyProvider *aProvider,
-                    uint32_t aSpacingStart, uint32_t aSpacingEnd,
+    void DrawGlyphs(gfxFont *aFont, Range aRange, gfxPoint *aPt,
+                    PropertyProvider *aProvider, Range aSpacingRange,
                     TextRunDrawParams& aParams, uint16_t aOrientation);
 
     // XXX this should be changed to a GlyphRun plus a maybe-null GlyphRun*,
     // for smaller size especially in the super-common one-glyphrun case
     AutoTArray<GlyphRun,1>        mGlyphRuns;
 
     void             *mUserData;
     gfxFontGroup     *mFontGroup; // addrefed on creation, but our reference
--- a/layout/generic/MathMLTextRunFactory.cpp
+++ b/layout/generic/MathMLTextRunFactory.cpp
@@ -768,22 +768,25 @@ MathMLTextRunFactory::RebuildTextRun(nsT
   } else {
     cachedChild = newFontGroup->MakeTextRun(
         convertedString.BeginReading(), convertedString.Length(),
         &innerParams, flags, aMFR);
     child = cachedChild.get();
   }
   if (!child)
     return;
+
+  typedef gfxTextRun::Range Range;
+
   // Copy potential linebreaks into child so they're preserved
   // (and also child will be shaped appropriately)
   NS_ASSERTION(convertedString.Length() == canBreakBeforeArray.Length(),
                "Dropped characters or break-before values somewhere!");
-  child->SetPotentialLineBreaks(0, canBreakBeforeArray.Length(),
-                                canBreakBeforeArray.Elements());
+  Range range(0, uint32_t(canBreakBeforeArray.Length()));
+  child->SetPotentialLineBreaks(range, canBreakBeforeArray.Elements());
   if (transformedChild) {
     transformedChild->FinishSettingProperties(aRefDrawTarget, aMFR);
   }
 
   if (mergeNeeded) {
     // Now merge multiple characters into one multi-glyph character as required
     NS_ASSERTION(charsToMergeArray.Length() == child->GetLength(),
                  "source length mismatch");
@@ -791,11 +794,11 @@ MathMLTextRunFactory::RebuildTextRun(nsT
                  "destination length mismatch");
     MergeCharactersInTextRun(aTextRun, child, charsToMergeArray.Elements(),
                              deletedCharsArray.Elements());
   } else {
     // No merging to do, so just copy; this produces a more optimized textrun.
     // We can't steal the data because the child may be cached and stealing
     // the data would break the cache.
     aTextRun->ResetGlyphRuns();
-    aTextRun->CopyGlyphDataFrom(child, 0, child->GetLength(), 0);
+    aTextRun->CopyGlyphDataFrom(child, Range(child), 0);
   }
 }
--- a/layout/generic/TextOverflow.cpp
+++ b/layout/generic/TextOverflow.cpp
@@ -248,17 +248,17 @@ nsDisplayTextOverflowMarker::PaintTextTo
 
   if (mStyle->mType == NS_STYLE_TEXT_OVERFLOW_ELLIPSIS) {
     gfxTextRun* textRun = GetEllipsisTextRun(mFrame);
     if (textRun) {
       NS_ASSERTION(!textRun->IsRightToLeft(),
                    "Ellipsis textruns should always be LTR!");
       gfxPoint gfxPt(pt.x, pt.y);
       textRun->Draw(aCtx->ThebesContext(), gfxPt, DrawMode::GLYPH_FILL,
-                    0, textRun->GetLength(), nullptr, nullptr, nullptr);
+                    gfxTextRun::Range(textRun), nullptr, nullptr, nullptr);
     }
   } else {
     RefPtr<nsFontMetrics> fm;
     nsLayoutUtils::GetFontMetricsForFrame(mFrame, getter_AddRefs(fm),
       nsLayoutUtils::FontSizeInflationFor(mFrame));
     nsLayoutUtils::DrawString(mFrame, *fm, aCtx, mStyle->mString.get(),
                               mStyle->mString.Length(), pt);
   }
--- a/layout/generic/nsTextFrame.cpp
+++ b/layout/generic/nsTextFrame.cpp
@@ -974,18 +974,19 @@ public:
               uint32_t aOffsetIntoTextRun)
       : mTextRun(aTextRun)
       , mDrawTarget(aDrawTarget)
       , mOffsetIntoTextRun(aOffsetIntoTextRun)
     {}
 
     virtual void SetBreaks(uint32_t aOffset, uint32_t aLength,
                            uint8_t* aBreakBefore) override {
-      if (mTextRun->SetPotentialLineBreaks(aOffset + mOffsetIntoTextRun, aLength,
-                                           aBreakBefore)) {
+      gfxTextRun::Range range(aOffset + mOffsetIntoTextRun,
+                              aOffset + mOffsetIntoTextRun + aLength);
+      if (mTextRun->SetPotentialLineBreaks(range, aBreakBefore)) {
         // Be conservative and assume that some breaks have been set
         mTextRun->ClearFlagBits(nsTextFrameUtils::TEXT_NO_BREAKS);
       }
     }
     
     virtual void SetCapitalization(uint32_t aOffset, uint32_t aLength,
                                    bool* aCapitalize) override {
       MOZ_ASSERT(mTextRun->GetFlags() & nsTextFrameUtils::TEXT_IS_TRANSFORMED,
@@ -2922,28 +2923,30 @@ static bool IsChineseOrJapanese(nsTextFr
     return false;
   }
   return nsStyleUtil::MatchesLanguagePrefix(language, MOZ_UTF16("ja")) ||
          nsStyleUtil::MatchesLanguagePrefix(language, MOZ_UTF16("zh"));
 }
 
 #ifdef DEBUG
 static bool IsInBounds(const gfxSkipCharsIterator& aStart, int32_t aContentLength,
-                         uint32_t aOffset, uint32_t aLength) {
-  if (aStart.GetSkippedOffset() > aOffset)
+                       gfxTextRun::Range aRange) {
+  if (aStart.GetSkippedOffset() > aRange.start)
     return false;
   if (aContentLength == INT32_MAX)
     return true;
   gfxSkipCharsIterator iter(aStart);
   iter.AdvanceOriginal(aContentLength);
-  return iter.GetSkippedOffset() >= aOffset + aLength;
+  return iter.GetSkippedOffset() >= aRange.end;
 }
 #endif
 
 class MOZ_STACK_CLASS PropertyProvider : public gfxTextRun::PropertyProvider {
+  typedef gfxTextRun::Range Range;
+
 public:
   /**
    * Use this constructor for reflow, when we don't know what text is
    * really mapped by the frame and we have a lot of other data around.
    * 
    * @param aLength can be INT32_MAX to indicate we cover all the text
    * associated with aFrame up to where its flow chain ends in the given
    * textrun. If INT32_MAX is passed, justification and hyphen-related methods
@@ -2997,40 +3000,38 @@ public:
     NS_ASSERTION(mTextRun, "Textrun not initialized!");
   }
 
   // Call this after construction if you're not going to reflow the text
   void InitializeForDisplay(bool aTrimAfter);
 
   void InitializeForMeasure();
 
-  virtual void GetSpacing(uint32_t aStart, uint32_t aLength, Spacing* aSpacing);
+  virtual void GetSpacing(Range aRange, Spacing* aSpacing);
   virtual gfxFloat GetHyphenWidth();
-  virtual void GetHyphenationBreaks(uint32_t aStart, uint32_t aLength,
-                                    bool* aBreakBefore);
+  virtual void GetHyphenationBreaks(Range aRange, bool* aBreakBefore);
   virtual int8_t GetHyphensOption() {
     return mTextStyle->mHyphens;
   }
 
   virtual already_AddRefed<DrawTarget> GetDrawTarget() {
     return CreateReferenceDrawTarget(GetFrame());
   }
 
   virtual uint32_t GetAppUnitsPerDevUnit() {
     return mTextRun->GetAppUnitsPerDevUnit();
   }
 
-  void GetSpacingInternal(uint32_t aStart, uint32_t aLength, Spacing* aSpacing,
-                          bool aIgnoreTabs);
+  void GetSpacingInternal(Range aRange, Spacing* aSpacing, bool aIgnoreTabs);
 
   /**
    * Compute the justification information in given DOM range, and fill data
    * necessary for computation of spacing.
    */
-  void ComputeJustification(int32_t aOffset, int32_t aLength);
+  void ComputeJustification(Range aRange);
 
   const nsStyleText* StyleText() { return mTextStyle; }
   nsTextFrame* GetFrame() { return mFrame; }
   // This may not be equal to the frame offset/length in because we may have
   // adjusted for whitespace trimming according to the state bits set in the frame
   // (for the static provider)
   const gfxSkipCharsIterator& GetStart() { return mStart; }
   // May return INT32_MAX if that was given to the constructor
@@ -3047,17 +3048,17 @@ public:
   }
 
   nsFontMetrics* GetFontMetrics() {
     if (!mFontMetrics)
       InitFontGroupAndFontMetrics();
     return mFontMetrics;
   }
 
-  void CalcTabWidths(uint32_t aTransformedStart, uint32_t aTransformedLength);
+  void CalcTabWidths(Range aTransformedRange);
 
   const gfxSkipCharsIterator& GetEndHint() { return mTempIterator; }
 
   const JustificationInfo& GetJustificationInfo() const
   {
     return mJustificationInfo;
   }
 
@@ -3142,26 +3143,26 @@ static void FindClusterEnd(gfxTextRun* a
       break;
     }
     aPos->AdvanceOriginal(1);
   }
   aPos->AdvanceOriginal(-1);
 }
 
 void
-PropertyProvider::ComputeJustification(int32_t aOffset, int32_t aLength)
+PropertyProvider::ComputeJustification(Range aRange)
 {
   bool isCJ = IsChineseOrJapanese(mFrame);
-  nsSkipCharsRunIterator
-    run(mStart, nsSkipCharsRunIterator::LENGTH_INCLUDES_SKIPPED, aLength);
-  run.SetOriginalOffset(aOffset);
+  nsSkipCharsRunIterator run(
+    mStart, nsSkipCharsRunIterator::LENGTH_INCLUDES_SKIPPED, aRange.Length());
+  run.SetOriginalOffset(aRange.start);
   mJustificationArrayStart = run.GetSkippedOffset();
 
   MOZ_ASSERT(mJustificationAssignments.IsEmpty());
-  mJustificationAssignments.SetCapacity(aLength);
+  mJustificationAssignments.SetCapacity(aRange.Length());
   while (run.NextRun()) {
     uint32_t originalOffset = run.GetOriginalOffset();
     uint32_t skippedOffset = run.GetSkippedOffset();
     uint32_t length = run.GetRunLength();
     mJustificationAssignments.SetLength(
       skippedOffset + length - mJustificationArrayStart);
 
     gfxSkipCharsIterator iter = run.GetPos();
@@ -3210,103 +3211,105 @@ PropertyProvider::ComputeJustification(i
     MOZ_ASSERT(mJustificationInfo.mInnerOpportunities > 0);
     mJustificationInfo.mInnerOpportunities--;
     mJustificationInfo.mIsEndJustifiable = true;
   }
 }
 
 // aStart, aLength in transformed string offsets
 void
-PropertyProvider::GetSpacing(uint32_t aStart, uint32_t aLength,
-                             Spacing* aSpacing)
-{
-  GetSpacingInternal(aStart, aLength, aSpacing,
+PropertyProvider::GetSpacing(Range aRange, Spacing* aSpacing)
+{
+  GetSpacingInternal(aRange, aSpacing,
                      (mTextRun->GetFlags() & nsTextFrameUtils::TEXT_HAS_TAB) == 0);
 }
 
 static bool
 CanAddSpacingAfter(gfxTextRun* aTextRun, uint32_t aOffset)
 {
   if (aOffset + 1 >= aTextRun->GetLength())
     return true;
   return aTextRun->IsClusterStart(aOffset + 1) &&
     aTextRun->IsLigatureGroupStart(aOffset + 1);
 }
 
 void
-PropertyProvider::GetSpacingInternal(uint32_t aStart, uint32_t aLength,
-                                     Spacing* aSpacing, bool aIgnoreTabs)
-{
-  NS_PRECONDITION(IsInBounds(mStart, mLength, aStart, aLength), "Range out of bounds");
+PropertyProvider::GetSpacingInternal(Range aRange, Spacing* aSpacing,
+                                     bool aIgnoreTabs)
+{
+  NS_PRECONDITION(IsInBounds(mStart, mLength, aRange), "Range out of bounds");
 
   uint32_t index;
-  for (index = 0; index < aLength; ++index) {
+  for (index = 0; index < aRange.Length(); ++index) {
     aSpacing[index].mBefore = 0.0;
     aSpacing[index].mAfter = 0.0;
   }
 
   // Find our offset into the original+transformed string
   gfxSkipCharsIterator start(mStart);
-  start.SetSkippedOffset(aStart);
+  start.SetSkippedOffset(aRange.start);
 
   // First, compute the word and letter spacing
   if (mWordSpacing || mLetterSpacing) {
     // Iterate over non-skipped characters
-    nsSkipCharsRunIterator
-      run(start, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY, aLength);
+    nsSkipCharsRunIterator run(
+        start, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY, aRange.Length());
     while (run.NextRun()) {
-      uint32_t runOffsetInSubstring = run.GetSkippedOffset() - aStart;
+      uint32_t runOffsetInSubstring = run.GetSkippedOffset() - aRange.start;
       gfxSkipCharsIterator iter = run.GetPos();
       for (int32_t i = 0; i < run.GetRunLength(); ++i) {
         if (CanAddSpacingAfter(mTextRun, run.GetSkippedOffset() + i)) {
           // End of a cluster, not in a ligature: put letter-spacing after it
           aSpacing[runOffsetInSubstring + i].mAfter += mLetterSpacing;
         }
         if (IsCSSWordSpacingSpace(mFrag, i + run.GetOriginalOffset(),
                                   mFrame, mTextStyle)) {
           // It kinda sucks, but space characters can be part of clusters,
           // and even still be whitespace (I think!)
           iter.SetSkippedOffset(run.GetSkippedOffset() + i);
           FindClusterEnd(mTextRun, run.GetOriginalOffset() + run.GetRunLength(),
                          &iter);
-          aSpacing[iter.GetSkippedOffset() - aStart].mAfter += mWordSpacing;
+          uint32_t runOffset = iter.GetSkippedOffset() - aRange.start;
+          aSpacing[runOffset].mAfter += mWordSpacing;
         }
       }
     }
   }
 
   // Ignore tab spacing rather than computing it, if the tab size is 0
   if (!aIgnoreTabs)
     aIgnoreTabs = mFrame->StyleText()->mTabSize == 0;
 
   // Now add tab spacing, if there is any
   if (!aIgnoreTabs) {
-    CalcTabWidths(aStart, aLength);
+    CalcTabWidths(aRange);
     if (mTabWidths) {
       mTabWidths->ApplySpacing(aSpacing,
-                               aStart - mStart.GetSkippedOffset(), aLength);
+                               aRange.start - mStart.GetSkippedOffset(),
+                               aRange.Length());
     }
   }
 
   // Now add in justification spacing
   if (mJustificationSpacing > 0 && mTotalJustificationGaps) {
     // If there is any spaces trimmed at the end, aStart + aLength may
     // be larger than the flags array. When that happens, we can simply
     // ignore those spaces.
     auto arrayEnd = mJustificationArrayStart +
       static_cast<uint32_t>(mJustificationAssignments.Length());
-    auto end = std::min(aStart + aLength, arrayEnd);
-    MOZ_ASSERT(aStart >= mJustificationArrayStart);
+    auto end = std::min(aRange.end, arrayEnd);
+    MOZ_ASSERT(aRange.start >= mJustificationArrayStart);
     JustificationApplicationState state(
         mTotalJustificationGaps, NSToCoordRound(mJustificationSpacing));
-    for (auto i = aStart; i < end; i++) {
+    for (auto i = aRange.start; i < end; i++) {
       const auto& assign =
         mJustificationAssignments[i - mJustificationArrayStart];
-      aSpacing[i - aStart].mBefore += state.Consume(assign.mGapsAtStart);
-      aSpacing[i - aStart].mAfter += state.Consume(assign.mGapsAtEnd);
+      uint32_t offset = i - aRange.start;
+      aSpacing[offset].mBefore += state.Consume(assign.mGapsAtStart);
+      aSpacing[offset].mAfter += state.Consume(assign.mGapsAtEnd);
     }
   }
 }
 
 static gfxFloat
 ComputeTabWidthAppUnits(nsIFrame* aFrame, gfxTextRun* aTextRun)
 {
   // Get the number of spaces from CSS -moz-tab-size
@@ -3326,68 +3329,68 @@ AdvanceToNextTab(gfxFloat aX, nsIFrame* 
 
   // Advance aX to the next multiple of *aCachedTabWidth. We must advance
   // by at least 1 appunit.
   // XXX should we make this 1 CSS pixel?
   return ceil((aX + 1)/(*aCachedTabWidth))*(*aCachedTabWidth);
 }
 
 void
-PropertyProvider::CalcTabWidths(uint32_t aStart, uint32_t aLength)
+PropertyProvider::CalcTabWidths(Range aRange)
 {
   if (!mTabWidths) {
     if (mReflowing && !mLineContainer) {
       // Intrinsic width computation does its own tab processing. We
       // just don't do anything here.
       return;
     }
     if (!mReflowing) {
       mTabWidths = static_cast<TabWidthStore*>
         (mFrame->Properties().Get(TabWidthProperty()));
 #ifdef DEBUG
       // If we're not reflowing, we should have already computed the
       // tab widths; check that they're available as far as the last
       // tab character present (if any)
-      for (uint32_t i = aStart + aLength; i > aStart; --i) {
+      for (uint32_t i = aRange.end; i > aRange.start; --i) {
         if (mTextRun->CharIsTab(i - 1)) {
           uint32_t startOffset = mStart.GetSkippedOffset();
           NS_ASSERTION(mTabWidths && mTabWidths->mLimit + startOffset >= i,
                        "Precomputed tab widths are missing!");
           break;
         }
       }
 #endif
       return;
     }
   }
 
   uint32_t startOffset = mStart.GetSkippedOffset();
-  MOZ_ASSERT(aStart >= startOffset, "wrong start offset");
-  MOZ_ASSERT(aStart + aLength <= startOffset + mLength, "beyond the end");
+  MOZ_ASSERT(aRange.start >= startOffset, "wrong start offset");
+  MOZ_ASSERT(aRange.end <= startOffset + mLength, "beyond the end");
   uint32_t tabsEnd =
     (mTabWidths ? mTabWidths->mLimit : mTabWidthsAnalyzedLimit) + startOffset;
-  if (tabsEnd < aStart + aLength) {
+  if (tabsEnd < aRange.end) {
     NS_ASSERTION(mReflowing,
                  "We need precomputed tab widths, but don't have enough.");
 
     gfxFloat tabWidth = -1;
-    for (uint32_t i = tabsEnd; i < aStart + aLength; ++i) {
+    for (uint32_t i = tabsEnd; i < aRange.end; ++i) {
       Spacing spacing;
-      GetSpacingInternal(i, 1, &spacing, true);
+      GetSpacingInternal(Range(i, i + 1), &spacing, true);
       mOffsetFromBlockOriginForTabs += spacing.mBefore;
 
       if (!mTextRun->CharIsTab(i)) {
         if (mTextRun->IsClusterStart(i)) {
           uint32_t clusterEnd = i + 1;
           while (clusterEnd < mTextRun->GetLength() &&
                  !mTextRun->IsClusterStart(clusterEnd)) {
             ++clusterEnd;
           }
           mOffsetFromBlockOriginForTabs +=
-            mTextRun->GetAdvanceWidth(i, clusterEnd - i, nullptr);
+            mTextRun->GetAdvanceWidth(Range(i, clusterEnd), nullptr);
         }
       } else {
         if (!mTabWidths) {
           mTabWidths = new TabWidthStore(mFrame->GetContentOffset());
           mFrame->Properties().Set(TabWidthProperty(), mTabWidths);
         }
         double nextTab = AdvanceToNextTab(mOffsetFromBlockOriginForTabs,
                 mFrame, mTextRun, &tabWidth);
@@ -3395,55 +3398,54 @@ PropertyProvider::CalcTabWidths(uint32_t
                 NSToIntRound(nextTab - mOffsetFromBlockOriginForTabs)));
         mOffsetFromBlockOriginForTabs = nextTab;
       }
 
       mOffsetFromBlockOriginForTabs += spacing.mAfter;
     }
 
     if (mTabWidths) {
-      mTabWidths->mLimit = aStart + aLength - startOffset;
+      mTabWidths->mLimit = aRange.end - startOffset;
     }
   }
 
   if (!mTabWidths) {
     // Delete any stale property that may be left on the frame
     mFrame->Properties().Delete(TabWidthProperty());
     mTabWidthsAnalyzedLimit = std::max(mTabWidthsAnalyzedLimit,
-                                       aStart + aLength - startOffset);
+                                       aRange.end - startOffset);
   }
 }
 
 gfxFloat
 PropertyProvider::GetHyphenWidth()
 {
   if (mHyphenWidth < 0) {
     mHyphenWidth = GetFontGroup()->GetHyphenWidth(this);
   }
   return mHyphenWidth + mLetterSpacing;
 }
 
 void
-PropertyProvider::GetHyphenationBreaks(uint32_t aStart, uint32_t aLength,
-                                       bool* aBreakBefore)
-{
-  NS_PRECONDITION(IsInBounds(mStart, mLength, aStart, aLength), "Range out of bounds");
+PropertyProvider::GetHyphenationBreaks(Range aRange, bool* aBreakBefore)
+{
+  NS_PRECONDITION(IsInBounds(mStart, mLength, aRange), "Range out of bounds");
   NS_PRECONDITION(mLength != INT32_MAX, "Can't call this with undefined length");
 
   if (!mTextStyle->WhiteSpaceCanWrap(mFrame) ||
       mTextStyle->mHyphens == NS_STYLE_HYPHENS_NONE)
   {
-    memset(aBreakBefore, false, aLength*sizeof(bool));
+    memset(aBreakBefore, false, aRange.Length() * sizeof(bool));
     return;
   }
 
   // Iterate through the original-string character runs
-  nsSkipCharsRunIterator
-    run(mStart, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY, aLength);
-  run.SetSkippedOffset(aStart);
+  nsSkipCharsRunIterator run(
+      mStart, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY, aRange.Length());
+  run.SetSkippedOffset(aRange.start);
   // We need to visit skipped characters so that we can detect SHY
   run.SetVisitSkipped();
 
   int32_t prevTrailingCharOffset = run.GetPos().GetOriginalOffset() - 1;
   bool allowHyphenBreakBeforeNextChar =
     prevTrailingCharOffset >= mStart.GetOriginalOffset() &&
     prevTrailingCharOffset < mStart.GetOriginalOffset() + mLength &&
     mFrag->CharAt(prevTrailingCharOffset) == CH_SHY;
@@ -3452,29 +3454,29 @@ PropertyProvider::GetHyphenationBreaks(u
     NS_ASSERTION(run.GetRunLength() > 0, "Shouldn't return zero-length runs");
     if (run.IsSkipped()) {
       // Check if there's a soft hyphen which would let us hyphenate before
       // the next non-skipped character. Don't look at soft hyphens followed
       // by other skipped characters, we won't use them.
       allowHyphenBreakBeforeNextChar =
         mFrag->CharAt(run.GetOriginalOffset() + run.GetRunLength() - 1) == CH_SHY;
     } else {
-      int32_t runOffsetInSubstring = run.GetSkippedOffset() - aStart;
+      int32_t runOffsetInSubstring = run.GetSkippedOffset() - aRange.start;
       memset(aBreakBefore + runOffsetInSubstring, false, run.GetRunLength()*sizeof(bool));
       // Don't allow hyphen breaks at the start of the line
       aBreakBefore[runOffsetInSubstring] = allowHyphenBreakBeforeNextChar &&
           (!(mFrame->GetStateBits() & TEXT_START_OF_LINE) ||
            run.GetSkippedOffset() > mStart.GetSkippedOffset());
       allowHyphenBreakBeforeNextChar = false;
     }
   }
 
   if (mTextStyle->mHyphens == NS_STYLE_HYPHENS_AUTO) {
-    for (uint32_t i = 0; i < aLength; ++i) {
-      if (mTextRun->CanHyphenateBefore(aStart + i)) {
+    for (uint32_t i = 0; i < aRange.Length(); ++i) {
+      if (mTextRun->CanHyphenateBefore(aRange.start + i)) {
         aBreakBefore[i] = true;
       }
     }
   }
 }
 
 void
 PropertyProvider::InitializeForDisplay(bool aTrimAfter)
@@ -3492,56 +3494,50 @@ PropertyProvider::InitializeForMeasure()
   nsTextFrame::TrimmedOffsets trimmed =
     mFrame->GetTrimmedOffsets(mFrag, true, false);
   mStart.SetOriginalOffset(trimmed.mStart);
   mLength = trimmed.mLength;
   SetupJustificationSpacing(false);
 }
 
 
-static uint32_t GetSkippedDistance(const gfxSkipCharsIterator& aStart,
-                                   const gfxSkipCharsIterator& aEnd)
-{
-  return aEnd.GetSkippedOffset() - aStart.GetSkippedOffset();
-}
-
 void
 PropertyProvider::SetupJustificationSpacing(bool aPostReflow)
 {
   NS_PRECONDITION(mLength != INT32_MAX, "Can't call this with undefined length");
 
   if (!(mFrame->GetStateBits() & TEXT_JUSTIFICATION_ENABLED))
     return;
 
   gfxSkipCharsIterator start(mStart), end(mStart);
   // We can't just use our mLength here; when InitializeForDisplay is
   // called with false for aTrimAfter, we still shouldn't be assigning
   // justification space to any trailing whitespace.
   nsTextFrame::TrimmedOffsets trimmed =
     mFrame->GetTrimmedOffsets(mFrag, true, aPostReflow);
   end.AdvanceOriginal(trimmed.mLength);
   gfxSkipCharsIterator realEnd(end);
-  ComputeJustification(start.GetOriginalOffset(),
-                       end.GetOriginalOffset() - start.GetOriginalOffset());
+  ComputeJustification(Range(uint32_t(start.GetOriginalOffset()),
+                             uint32_t(end.GetOriginalOffset())));
 
   auto assign = mFrame->GetJustificationAssignment();
   mTotalJustificationGaps =
     JustificationUtils::CountGaps(mJustificationInfo, assign);
   if (!mTotalJustificationGaps || mJustificationAssignments.IsEmpty()) {
     // Nothing to do, nothing is justifiable and we shouldn't have any
     // justification space assigned
     return;
   }
 
   // Remember that textrun measurements are in the run's orientation,
   // so its advance "width" is actually a height in vertical writing modes,
   // corresponding to the inline-direction of the frame.
   gfxFloat naturalWidth =
-    mTextRun->GetAdvanceWidth(mStart.GetSkippedOffset(),
-                              GetSkippedDistance(mStart, realEnd), this);
+    mTextRun->GetAdvanceWidth(Range(mStart.GetSkippedOffset(),
+                                    realEnd.GetSkippedOffset()), this);
   if (mFrame->GetStateBits() & TEXT_HYPHEN_BREAK) {
     naturalWidth += GetHyphenWidth();
   }
   mJustificationSpacing = mFrame->ISize() - naturalWidth;
   if (mJustificationSpacing <= 0) {
     // No space available
     return;
   }
@@ -5735,99 +5731,99 @@ public:
   /**
    * aStart and aLength are in the original string. aSelectionDetails is
    * according to the original string.
    * @param aXOffset the offset from the origin of the frame to the start
    * of the text (the left baseline origin for LTR, the right baseline origin
    * for RTL)
    */
   SelectionIterator(SelectionDetails** aSelectionDetails,
-                    int32_t aStart, int32_t aLength,
-                    PropertyProvider& aProvider, gfxTextRun* aTextRun,
-                    gfxFloat aXOffset);
+                    gfxTextRun::Range aRange, PropertyProvider& aProvider,
+                    gfxTextRun* aTextRun, gfxFloat aXOffset);
 
   /**
    * Returns the next segment of uniformly selected (or not) text.
    * @param aXOffset the offset from the origin of the frame to the start
    * of the text (the left baseline origin for LTR, the right baseline origin
    * for RTL)
-   * @param aOffset the transformed string offset of the text for this segment
-   * @param aLength the transformed string length of the text for this segment
+   * @param aRange the transformed string range of the text for this segment
    * @param aHyphenWidth if a hyphen is to be rendered after the text, the
    * width of the hyphen, otherwise zero
    * @param aType the selection type for this segment
    * @param aStyle the selection style for this segment
    * @return false if there are no more segments
    */
-  bool GetNextSegment(gfxFloat* aXOffset, uint32_t* aOffset, uint32_t* aLength,
-                        gfxFloat* aHyphenWidth, SelectionType* aType,
-                        TextRangeStyle* aStyle);
+  bool GetNextSegment(gfxFloat* aXOffset, gfxTextRun::Range* aRange,
+                      gfxFloat* aHyphenWidth, SelectionType* aType,
+                      TextRangeStyle* aStyle);
   void UpdateWithAdvance(gfxFloat aAdvance) {
     mXOffset += aAdvance*mTextRun->GetDirection();
   }
 
 private:
   SelectionDetails**      mSelectionDetails;
   PropertyProvider&       mProvider;
   gfxTextRun*             mTextRun;
   gfxSkipCharsIterator    mIterator;
-  int32_t                 mOriginalStart;
-  int32_t                 mOriginalEnd;
+  gfxTextRun::Range       mOriginalRange;
   gfxFloat                mXOffset;
 };
 
 SelectionIterator::SelectionIterator(SelectionDetails** aSelectionDetails,
-    int32_t aStart, int32_t aLength, PropertyProvider& aProvider,
-    gfxTextRun* aTextRun, gfxFloat aXOffset)
+                                     gfxTextRun::Range aRange,
+                                     PropertyProvider& aProvider,
+                                     gfxTextRun* aTextRun, gfxFloat aXOffset)
   : mSelectionDetails(aSelectionDetails), mProvider(aProvider),
     mTextRun(aTextRun), mIterator(aProvider.GetStart()),
-    mOriginalStart(aStart), mOriginalEnd(aStart + aLength),
-    mXOffset(aXOffset)
-{
-  mIterator.SetOriginalOffset(aStart);
+    mOriginalRange(aRange), mXOffset(aXOffset)
+{
+  mIterator.SetOriginalOffset(aRange.start);
 }
 
 bool SelectionIterator::GetNextSegment(gfxFloat* aXOffset,
-    uint32_t* aOffset, uint32_t* aLength, gfxFloat* aHyphenWidth,
-    SelectionType* aType, TextRangeStyle* aStyle)
-{
-  if (mIterator.GetOriginalOffset() >= mOriginalEnd)
+                                       gfxTextRun::Range* aRange,
+                                       gfxFloat* aHyphenWidth,
+                                       SelectionType* aType,
+                                       TextRangeStyle* aStyle)
+{
+  if (mIterator.GetOriginalOffset() >= int32_t(mOriginalRange.end))
     return false;
   
   // save offset into transformed string now
   uint32_t runOffset = mIterator.GetSkippedOffset();
   
-  int32_t index = mIterator.GetOriginalOffset() - mOriginalStart;
+  uint32_t index = mIterator.GetOriginalOffset() - mOriginalRange.start;
   SelectionDetails* sdptr = mSelectionDetails[index];
   SelectionType type =
     sdptr ? sdptr->mType : nsISelectionController::SELECTION_NONE;
   TextRangeStyle style;
   if (sdptr) {
     style = sdptr->mTextRangeStyle;
   }
-  for (++index; mOriginalStart + index < mOriginalEnd; ++index) {
+  for (++index; index < mOriginalRange.Length(); ++index) {
     if (sdptr != mSelectionDetails[index])
       break;
   }
-  mIterator.SetOriginalOffset(index + mOriginalStart);
+  mIterator.SetOriginalOffset(index + mOriginalRange.start);
 
   // Advance to the next cluster boundary
-  while (mIterator.GetOriginalOffset() < mOriginalEnd &&
+  while (mIterator.GetOriginalOffset() < int32_t(mOriginalRange.end) &&
          !mIterator.IsOriginalCharSkipped() &&
          !mTextRun->IsClusterStart(mIterator.GetSkippedOffset())) {
     mIterator.AdvanceOriginal(1);
   }
 
   bool haveHyphenBreak =
     (mProvider.GetFrame()->GetStateBits() & TEXT_HYPHEN_BREAK) != 0;
-  *aOffset = runOffset;
-  *aLength = mIterator.GetSkippedOffset() - runOffset;
+  aRange->start = runOffset;
+  aRange->end = mIterator.GetSkippedOffset();
   *aXOffset = mXOffset;
   *aHyphenWidth = 0;
-  if (mIterator.GetOriginalOffset() == mOriginalEnd && haveHyphenBreak) {
+  if (mIterator.GetOriginalOffset() == int32_t(mOriginalRange.end) &&
+      haveHyphenBreak) {
     *aHyphenWidth = mProvider.GetHyphenWidth();
   }
   *aType = type;
   *aStyle = style;
   return true;
 }
 
 static void
@@ -5846,17 +5842,17 @@ AddHyphenToMetrics(nsTextFrame* aTextFra
     hyphenTextRun->MeasureText(aBoundingBoxType, aDrawTarget);
   if (aTextFrame->GetWritingMode().IsLineInverted()) {
     hyphenMetrics.mBoundingBox.y = -hyphenMetrics.mBoundingBox.YMost();
   }
   aMetrics->CombineWith(hyphenMetrics, aBaseTextRun->IsRightToLeft());
 }
 
 void
-nsTextFrame::PaintOneShadow(uint32_t aOffset, uint32_t aLength,
+nsTextFrame::PaintOneShadow(Range aRange,
                             nsCSSShadowItem* aShadowDetails,
                             PropertyProvider* aProvider, const nsRect& aDirtyRect,
                             const gfxPoint& aFramePt, const gfxPoint& aTextBaselinePt,
                             gfxContext* aCtx, const nscolor& aForegroundColor,
                             const nsCharClipDisplayItem::ClipEdges& aClipEdges,
                             nscoord aLeftSideOffset, gfxRect& aBoundingBox,
                             uint32_t aBlurFlags)
 {
@@ -5917,58 +5913,58 @@ nsTextFrame::PaintOneShadow(uint32_t aOf
 
   // Draw the text onto our alpha-only surface to capture the alpha values.
   // Remember that the box blur context has a device offset on it, so we don't need to
   // translate any coordinates to fit on the surface.
   gfxFloat advanceWidth;
   gfxRect dirtyRect(aDirtyRect.x, aDirtyRect.y,
                     aDirtyRect.width, aDirtyRect.height);
   DrawText(shadowContext, dirtyRect, aFramePt + shadowOffset,
-           aTextBaselinePt + shadowOffset, aOffset, aLength, *aProvider,
+           aTextBaselinePt + shadowOffset, aRange, *aProvider,
            nsTextPaintStyle(this),
            aCtx == shadowContext ? shadowColor : NS_RGB(0, 0, 0), aClipEdges,
            advanceWidth, (GetStateBits() & TEXT_HYPHEN_BREAK) != 0,
            decorationOverrideColor);
 
   contextBoxBlur.DoPaint();
   aCtx->Restore();
 }
 
 // Paints selection backgrounds and text in the correct colors. Also computes
 // aAllTypes, the union of all selection types that are applying to this text.
 bool
 nsTextFrame::PaintTextWithSelectionColors(gfxContext* aCtx,
     const gfxPoint& aFramePt, const gfxPoint& aTextBaselinePt,
     const gfxRect& aDirtyRect,
     PropertyProvider& aProvider,
-    uint32_t aContentOffset, uint32_t aContentLength,
+    Range aContentRange,
     nsTextPaintStyle& aTextPaintStyle, SelectionDetails* aDetails,
     SelectionType* aAllTypes,
     const nsCharClipDisplayItem::ClipEdges& aClipEdges,
     nsTextFrame::DrawPathCallbacks* aCallbacks)
 {
   // Figure out which selections control the colors to use for each character.
   AutoTArray<SelectionDetails*,BIG_TEXT_NODE_SIZE> prevailingSelectionsBuffer;
   SelectionDetails** prevailingSelections =
-    prevailingSelectionsBuffer.AppendElements(aContentLength, fallible);
+    prevailingSelectionsBuffer.AppendElements(aContentRange.Length(), fallible);
   if (!prevailingSelections) {
     return false;
   }
 
   SelectionType allTypes = 0;
-  for (uint32_t i = 0; i < aContentLength; ++i) {
+  for (uint32_t i = 0; i < aContentRange.Length(); ++i) {
     prevailingSelections[i] = nullptr;
   }
 
   SelectionDetails *sdptr = aDetails;
   bool anyBackgrounds = false;
   while (sdptr) {
-    int32_t start = std::max(0, sdptr->mStart - int32_t(aContentOffset));
-    int32_t end = std::min(int32_t(aContentLength),
-                         sdptr->mEnd - int32_t(aContentOffset));
+    int32_t start = std::max(0, sdptr->mStart - int32_t(aContentRange.start));
+    int32_t end = std::min(int32_t(aContentRange.Length()),
+                           sdptr->mEnd - int32_t(aContentRange.start));
     SelectionType type = sdptr->mType;
     if (start < end) {
       allTypes |= type;
       // Ignore selections that don't set colors
       nscolor foreground, background;
       if (GetSelectionTextColors(type, aTextPaintStyle, sdptr->mTextRangeStyle,
                                  &foreground, &background)) {
         if (NS_GET_A(background) > 0) {
@@ -5991,33 +5987,33 @@ nsTextFrame::PaintTextWithSelectionColor
     // Nothing is selected in the given text range. XXX can this still occur?
     return false;
   }
 
   bool vertical = mTextRun->IsVertical();
   const gfxFloat startIOffset = vertical ?
     aTextBaselinePt.y - aFramePt.y : aTextBaselinePt.x - aFramePt.x;
   gfxFloat iOffset, hyphenWidth;
-  uint32_t offset, length; // in transformed string
+  Range range; // in transformed string
   SelectionType type;
   TextRangeStyle rangeStyle;
   // Draw background colors
   if (anyBackgrounds) {
     int32_t appUnitsPerDevPixel = aTextPaintStyle.PresContext()->AppUnitsPerDevPixel();
     LayoutDeviceRect dirtyRect = AppUnitGfxRectToDevRect(aDirtyRect, appUnitsPerDevPixel);
-    SelectionIterator iterator(prevailingSelections, aContentOffset, aContentLength,
+    SelectionIterator iterator(prevailingSelections, aContentRange,
                                aProvider, mTextRun, startIOffset);
-    while (iterator.GetNextSegment(&iOffset, &offset, &length, &hyphenWidth,
+    while (iterator.GetNextSegment(&iOffset, &range, &hyphenWidth,
                                    &type, &rangeStyle)) {
       nscolor foreground, background;
       GetSelectionTextColors(type, aTextPaintStyle, rangeStyle,
                              &foreground, &background);
       // Draw background color
       gfxFloat advance = hyphenWidth +
-        mTextRun->GetAdvanceWidth(offset, length, &aProvider);
+        mTextRun->GetAdvanceWidth(range, &aProvider);
       if (NS_GET_A(background) > 0) {
         gfxRect bgRect;
         gfxFloat offs = iOffset - (mTextRun->IsInlineReversed() ? advance : 0);
         if (vertical) {
           bgRect = gfxRect(aFramePt.x, aFramePt.y + offs,
                            GetSize().width, advance);
         } else {
           bgRect = gfxRect(aFramePt.x + offs, aFramePt.y,
@@ -6030,83 +6026,82 @@ nsTextFrame::PaintTextWithSelectionColor
       iterator.UpdateWithAdvance(advance);
     }
   }
   
   // Draw text
   const nsStyleText* textStyle = StyleText();
   nsRect dirtyRect(aDirtyRect.x, aDirtyRect.y,
                    aDirtyRect.width, aDirtyRect.height);
-  SelectionIterator iterator(prevailingSelections, aContentOffset, aContentLength,
+  SelectionIterator iterator(prevailingSelections, aContentRange,
                              aProvider, mTextRun, startIOffset);
-  while (iterator.GetNextSegment(&iOffset, &offset, &length, &hyphenWidth,
+  while (iterator.GetNextSegment(&iOffset, &range, &hyphenWidth,
                                  &type, &rangeStyle)) {
     nscolor foreground, background;
     GetSelectionTextColors(type, aTextPaintStyle, rangeStyle,
                            &foreground, &background);
     gfxPoint textBaselinePt = vertical ?
       gfxPoint(aTextBaselinePt.x, aFramePt.y + iOffset) :
       gfxPoint(aFramePt.x + iOffset, aTextBaselinePt.y);
 
     // Determine what shadow, if any, to draw - either from textStyle
     // or from the ::-moz-selection pseudo-class if specified there
     nsCSSShadowArray* shadow = textStyle->GetTextShadow();
     GetSelectionTextShadow(this, type, aTextPaintStyle, &shadow);
     if (shadow) {
       nscoord startEdge = iOffset;
       if (mTextRun->IsInlineReversed()) {
-        startEdge -= mTextRun->GetAdvanceWidth(offset, length, &aProvider) +
-            hyphenWidth;
-      }
-      PaintShadows(shadow, offset, length, dirtyRect, aFramePt, textBaselinePt,
+        startEdge -= hyphenWidth +
+          mTextRun->GetAdvanceWidth(range, &aProvider);
+      }
+      PaintShadows(shadow, range, dirtyRect, aFramePt, textBaselinePt,
           startEdge, aProvider, foreground, aClipEdges, aCtx);
     }
 
     // Draw text segment
     gfxFloat advance;
     DrawText(aCtx, aDirtyRect, aFramePt, textBaselinePt,
-             offset, length, aProvider, aTextPaintStyle, foreground, aClipEdges,
+             range, aProvider, aTextPaintStyle, foreground, aClipEdges,
              advance, hyphenWidth > 0, nullptr, nullptr, aCallbacks);
     advance += hyphenWidth;
     iterator.UpdateWithAdvance(advance);
   }
   return true;
 }
 
 void
 nsTextFrame::PaintTextSelectionDecorations(gfxContext* aCtx,
     const gfxPoint& aFramePt,
     const gfxPoint& aTextBaselinePt, const gfxRect& aDirtyRect,
-    PropertyProvider& aProvider,
-    uint32_t aContentOffset, uint32_t aContentLength,
+    PropertyProvider& aProvider, Range aContentRange,
     nsTextPaintStyle& aTextPaintStyle, SelectionDetails* aDetails,
     SelectionType aSelectionType,
     nsTextFrame::DrawPathCallbacks* aCallbacks)
 {
   // Hide text decorations if we're currently hiding @font-face fallback text
   if (aProvider.GetFontGroup()->ShouldSkipDrawing())
     return;
 
   // Figure out which characters will be decorated for this selection.
   AutoTArray<SelectionDetails*, BIG_TEXT_NODE_SIZE> selectedCharsBuffer;
   SelectionDetails** selectedChars =
-    selectedCharsBuffer.AppendElements(aContentLength, fallible);
+    selectedCharsBuffer.AppendElements(aContentRange.Length(), fallible);
   if (!selectedChars) {
     return;
   }
-  for (uint32_t i = 0; i < aContentLength; ++i) {
+  for (uint32_t i = 0; i < aContentRange.Length(); ++i) {
     selectedChars[i] = nullptr;
   }
 
   SelectionDetails *sdptr = aDetails;
   while (sdptr) {
     if (sdptr->mType == aSelectionType) {
-      int32_t start = std::max(0, sdptr->mStart - int32_t(aContentOffset));
-      int32_t end = std::min(int32_t(aContentLength),
-                           sdptr->mEnd - int32_t(aContentOffset));
+      int32_t start = std::max(0, sdptr->mStart - int32_t(aContentRange.start));
+      int32_t end = std::min(int32_t(aContentRange.Length()),
+                             sdptr->mEnd - int32_t(aContentRange.start));
       for (int32_t i = start; i < end; ++i) {
         selectedChars[i] = sdptr;
       }
     }
     sdptr = sdptr->mNext;
   }
 
   gfxFont* firstFont = aProvider.GetFontGroup()->GetFirstValidFont();
@@ -6123,37 +6118,37 @@ nsTextFrame::PaintTextSelectionDecoratio
     // The potential adjustment from using gfxFontGroup::GetUnderlineOffset
     // is only valid for horizontal font metrics.
     decorationMetrics.underlineOffset =
       aProvider.GetFontGroup()->GetUnderlineOffset();
   }
 
   gfxFloat startIOffset =
     verticalRun ? aTextBaselinePt.y - aFramePt.y : aTextBaselinePt.x - aFramePt.x;
-  SelectionIterator iterator(selectedChars, aContentOffset, aContentLength,
+  SelectionIterator iterator(selectedChars, aContentRange,
                              aProvider, mTextRun, startIOffset);
   gfxFloat iOffset, hyphenWidth;
-  uint32_t offset, length;
+  Range range;
   int32_t app = aTextPaintStyle.PresContext()->AppUnitsPerDevPixel();
   // XXX aTextBaselinePt is in AppUnits, shouldn't it be nsFloatPoint?
   Point pt;
   if (verticalRun) {
     pt.x = (aTextBaselinePt.x - mAscent) / app;
   } else {
     pt.y = (aTextBaselinePt.y - mAscent) / app;
   }
   gfxRect dirtyRect(aDirtyRect.x / app, aDirtyRect.y / app,
                     aDirtyRect.width / app, aDirtyRect.height / app);
   gfxFloat decorationOffsetDir = mTextRun->IsSidewaysLeft() ? -1.0 : 1.0;
   SelectionType type;
   TextRangeStyle selectedStyle;
-  while (iterator.GetNextSegment(&iOffset, &offset, &length, &hyphenWidth,
+  while (iterator.GetNextSegment(&iOffset, &range, &hyphenWidth,
                                  &type, &selectedStyle)) {
     gfxFloat advance = hyphenWidth +
-      mTextRun->GetAdvanceWidth(offset, length, &aProvider);
+      mTextRun->GetAdvanceWidth(range, &aProvider);
     if (type == aSelectionType) {
       if (verticalRun) {
         pt.y = (aFramePt.y + iOffset -
                (mTextRun->IsInlineReversed() ? advance : 0)) / app;
       } else {
         pt.x = (aFramePt.x + iOffset -
                (mTextRun->IsInlineReversed() ? advance : 0)) / app;
       }
@@ -6169,32 +6164,32 @@ nsTextFrame::PaintTextSelectionDecoratio
   }
 }
 
 bool
 nsTextFrame::PaintTextWithSelection(gfxContext* aCtx,
     const gfxPoint& aFramePt,
     const gfxPoint& aTextBaselinePt, const gfxRect& aDirtyRect,
     PropertyProvider& aProvider,
-    uint32_t aContentOffset, uint32_t aContentLength,
+    Range aContentRange,
     nsTextPaintStyle& aTextPaintStyle,
     const nsCharClipDisplayItem::ClipEdges& aClipEdges,
     gfxTextContextPaint* aContextPaint,
     nsTextFrame::DrawPathCallbacks* aCallbacks)
 {
   NS_ASSERTION(GetContent()->IsSelectionDescendant(), "wrong paint path");
 
   SelectionDetails* details = GetSelectionDetails();
   if (!details) {
     return false;
   }
 
   SelectionType allTypes;
   if (!PaintTextWithSelectionColors(aCtx, aFramePt, aTextBaselinePt, aDirtyRect,
-                                    aProvider, aContentOffset, aContentLength,
+                                    aProvider, aContentRange,
                                     aTextPaintStyle, details, &allTypes,
                                     aClipEdges, aCallbacks)) {
     DestroySelectionDetails(details);
     return false;
   }
   // Iterate through just the selection types that paint decorations and
   // paint decorations for any that actually occur in this frame. Paint
   // higher-numbered selection types below lower-numered ones on the
@@ -6203,30 +6198,29 @@ nsTextFrame::PaintTextWithSelection(gfxC
   for (int32_t i = nsISelectionController::NUM_SELECTIONTYPES - 1;
        i >= 1; --i) {
     SelectionType type = 1 << (i - 1);
     if (allTypes & type) {
       // There is some selection of this type. Try to paint its decorations
       // (there might not be any for this type but that's OK,
       // PaintTextSelectionDecorations will exit early).
       PaintTextSelectionDecorations(aCtx, aFramePt, aTextBaselinePt, aDirtyRect,
-                                    aProvider, aContentOffset, aContentLength,
-                                    aTextPaintStyle, details, type,
-                                    aCallbacks);
+                                    aProvider, aContentRange, aTextPaintStyle,
+                                    details, type, aCallbacks);
     }
   }
 
   DestroySelectionDetails(details);
   return true;
 }
 
 void
 nsTextFrame::DrawEmphasisMarks(gfxContext* aContext, WritingMode aWM,
                                const gfxPoint& aTextBaselinePt,
-                               uint32_t aOffset, uint32_t aLength,
+                               Range aRange,
                                const nscolor* aDecorationOverrideColor,
                                PropertyProvider& aProvider)
 {
   const auto info = Properties().Get(EmphasisMarkProperty());
   if (!info) {
     return;
   }
 
@@ -6239,17 +6233,17 @@ nsTextFrame::DrawEmphasisMarks(gfxContex
   } else {
     if (aWM.IsVerticalRL()) {
       pt.x -= info->baselineOffset;
     } else {
       pt.x += info->baselineOffset;
     }
   }
   mTextRun->DrawEmphasisMarks(aContext, info->textRun, info->advance,
-                              pt, aOffset, aLength, &aProvider);
+                              pt, aRange, &aProvider);
 }
 
 nscolor
 nsTextFrame::GetCaretColorAt(int32_t aOffset)
 {
   NS_PRECONDITION(aOffset >= 0, "aOffset must be positive");
 
   nscolor result = nsFrame::GetCaretColorAt(aOffset);
@@ -6299,23 +6293,23 @@ nsTextFrame::GetCaretColorAt(int32_t aOf
     }
     sdptr = sdptr->mNext;
   }
 
   DestroySelectionDetails(details);
   return result;
 }
 
-static uint32_t
-ComputeTransformedLength(PropertyProvider& aProvider)
+static gfxTextRun::Range
+ComputeTransformedRange(PropertyProvider& aProvider)
 {
   gfxSkipCharsIterator iter(aProvider.GetStart());
   uint32_t start = iter.GetSkippedOffset();
   iter.AdvanceOriginal(aProvider.GetOriginalLength());
-  return iter.GetSkippedOffset() - start;
+  return gfxTextRun::Range(start, iter.GetSkippedOffset());
 }
 
 bool
 nsTextFrame::MeasureCharClippedText(nscoord aVisIStartEdge,
                                     nscoord aVisIEndEdge,
                                     nscoord* aSnappedStartEdge,
                                     nscoord* aSnappedEndEdge)
 {
@@ -6325,18 +6319,19 @@ nsTextFrame::MeasureCharClippedText(nsco
   gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
   if (!mTextRun)
     return false;
 
   PropertyProvider provider(this, iter, nsTextFrame::eInflated);
   // Trim trailing whitespace
   provider.InitializeForDisplay(true);
 
-  uint32_t startOffset = provider.GetStart().GetSkippedOffset();
-  uint32_t maxLength = ComputeTransformedLength(provider);
+  Range range = ComputeTransformedRange(provider);
+  uint32_t startOffset = range.start;
+  uint32_t maxLength = range.Length();
   return MeasureCharClippedText(provider, aVisIStartEdge, aVisIEndEdge,
                                 &startOffset, &maxLength,
                                 aSnappedStartEdge, aSnappedEndEdge);
 }
 
 static uint32_t GetClusterLength(gfxTextRun* aTextRun,
                                  uint32_t    aStartOffset,
                                  uint32_t    aMaxLength,
@@ -6376,18 +6371,18 @@ nsTextFrame::MeasureCharClippedText(Prop
   const bool rtl = mTextRun->IsRightToLeft();
   gfxFloat advanceWidth = 0;
   const nscoord startEdge = rtl ? aVisIEndEdge : aVisIStartEdge;
   if (startEdge > 0) {
     const gfxFloat maxAdvance = gfxFloat(startEdge);
     while (maxLength > 0) {
       uint32_t clusterLength =
         GetClusterLength(mTextRun, offset, maxLength, rtl);
-      advanceWidth +=
-        mTextRun->GetAdvanceWidth(offset, clusterLength, &aProvider);
+      advanceWidth += mTextRun->
+        GetAdvanceWidth(Range(offset, offset + clusterLength), &aProvider);
       maxLength -= clusterLength;
       offset += clusterLength;
       if (advanceWidth >= maxAdvance) {
         break;
       }
     }
     nscoord* snappedStartEdge = rtl ? aSnappedEndEdge : aSnappedStartEdge;
     *snappedStartEdge = NSToCoordFloor(advanceWidth);
@@ -6395,18 +6390,18 @@ nsTextFrame::MeasureCharClippedText(Prop
   }
 
   const nscoord endEdge = rtl ? aVisIStartEdge : aVisIEndEdge;
   if (endEdge > 0) {
     const gfxFloat maxAdvance = gfxFloat(frameISize - endEdge);
     while (maxLength > 0) {
       uint32_t clusterLength =
         GetClusterLength(mTextRun, offset, maxLength, rtl);
-      gfxFloat nextAdvance = advanceWidth +
-        mTextRun->GetAdvanceWidth(offset, clusterLength, &aProvider);
+      gfxFloat nextAdvance = advanceWidth + mTextRun->GetAdvanceWidth(
+          Range(offset, offset + clusterLength), &aProvider);
       if (nextAdvance > maxAdvance) {
         break;
       }
       // This cluster fits, include it.
       advanceWidth = nextAdvance;
       maxLength -= clusterLength;
       offset += clusterLength;
     }
@@ -6415,32 +6410,32 @@ nsTextFrame::MeasureCharClippedText(Prop
     *snappedEndEdge = NSToCoordFloor(gfxFloat(frameISize) - advanceWidth);
   }
   *aMaxLength = maxLength;
   return maxLength != 0;
 }
 
 void
 nsTextFrame::PaintShadows(nsCSSShadowArray* aShadow,
-                          uint32_t aOffset, uint32_t aLength,
+                          Range aRange,
                           const nsRect& aDirtyRect,
                           const gfxPoint& aFramePt,
                           const gfxPoint& aTextBaselinePt,
                           nscoord aLeftEdgeOffset,
                           PropertyProvider& aProvider,
                           nscolor aForegroundColor,
                           const nsCharClipDisplayItem::ClipEdges& aClipEdges,
                           gfxContext* aCtx)
 {
   if (!aShadow) {
     return;
   }
 
   gfxTextRun::Metrics shadowMetrics =
-    mTextRun->MeasureText(aOffset, aLength, gfxFont::LOOSE_INK_EXTENTS,
+    mTextRun->MeasureText(aRange, gfxFont::LOOSE_INK_EXTENTS,
                           nullptr, &aProvider);
   if (GetWritingMode().IsLineInverted()) {
     Swap(shadowMetrics.mAscent, shadowMetrics.mDescent);
     shadowMetrics.mBoundingBox.y = -shadowMetrics.mBoundingBox.YMost();
   }
   if (GetStateBits() & TEXT_HYPHEN_BREAK) {
     AddHyphenToMetrics(this, mTextRun, &shadowMetrics,
                        gfxFont::LOOSE_INK_EXTENTS, aCtx->GetDrawTarget());
@@ -6465,18 +6460,17 @@ nsTextFrame::PaintShadows(nsCSSShadowArr
   }
 
   if (mTextRun->IsVertical()) {
     Swap(shadowMetrics.mBoundingBox.x, shadowMetrics.mBoundingBox.y);
     Swap(shadowMetrics.mBoundingBox.width, shadowMetrics.mBoundingBox.height);
   }
 
   for (uint32_t i = aShadow->Length(); i > 0; --i) {
-    PaintOneShadow(aOffset, aLength,
-                   aShadow->ShadowAt(i - 1), &aProvider,
+    PaintOneShadow(aRange, aShadow->ShadowAt(i - 1), &aProvider,
                    aDirtyRect, aFramePt, aTextBaselinePt, aCtx,
                    aForegroundColor, aClipEdges,
                    aLeftEdgeOffset,
                    shadowMetrics.mBoundingBox,
                    blurFlags);
   }
 }
 
@@ -6520,18 +6514,19 @@ nsTextFrame::PaintText(nsRenderingContex
                                            -mAscent);
     }
     textBaselinePt.y = reversed ? aPt.y + GetSize().height : aPt.y;
   } else {
     textBaselinePt =
       gfxPoint(reversed ? gfxFloat(aPt.x + frameWidth) : framePt.x,
                nsLayoutUtils::GetSnappedBaselineY(this, ctx, aPt.y, mAscent));
   }
-  uint32_t startOffset = provider.GetStart().GetSkippedOffset();
-  uint32_t maxLength = ComputeTransformedLength(provider);
+  Range range = ComputeTransformedRange(provider);
+  uint32_t startOffset = range.start;
+  uint32_t maxLength = range.Length();
   nscoord snappedStartEdge, snappedEndEdge;
   if (!MeasureCharClippedText(provider, aItem.mVisIStartEdge, aItem.mVisIEndEdge,
          &startOffset, &maxLength, &snappedStartEdge, &snappedEndEdge)) {
     return;
   }
   if (verticalRun) {
     textBaselinePt.y += reversed ? -snappedEndEdge : snappedStartEdge;
   } else {
@@ -6543,109 +6538,109 @@ nsTextFrame::PaintText(nsRenderingContex
   textPaintStyle.SetResolveColors(!aCallbacks);
 
   gfxRect dirtyRect(aDirtyRect.x, aDirtyRect.y,
                     aDirtyRect.width, aDirtyRect.height);
   // Fork off to the (slower) paint-with-selection path if necessary.
   if (aItem.mIsFrameSelected.value()) {
     MOZ_ASSERT(aOpacity == 1.0f, "We don't support opacity with selections!");
     gfxSkipCharsIterator tmp(provider.GetStart());
-    int32_t contentOffset = tmp.ConvertSkippedToOriginal(startOffset);
-    int32_t contentLength =
-      tmp.ConvertSkippedToOriginal(startOffset + maxLength) - contentOffset;
+    Range contentRange(
+      uint32_t(tmp.ConvertSkippedToOriginal(startOffset)),
+      uint32_t(tmp.ConvertSkippedToOriginal(startOffset + maxLength)));
     if (PaintTextWithSelection(ctx, framePt, textBaselinePt, dirtyRect,
-                               provider, contentOffset, contentLength,
-                               textPaintStyle, clipEdges, aContextPaint,
-                               aCallbacks)) {
+                               provider, contentRange, textPaintStyle,
+                               clipEdges, aContextPaint, aCallbacks)) {
       return;
     }
   }
 
   nscolor foregroundColor = textPaintStyle.GetTextColor();
   if (aOpacity != 1.0f) {
     gfx::Color gfxColor = gfx::Color::FromABGR(foregroundColor);
     gfxColor.a *= aOpacity;
     foregroundColor = gfxColor.ToABGR();
   }
 
+  range = Range(startOffset, startOffset + maxLength);
   if (!aCallbacks) {
     const nsStyleText* textStyle = StyleText();
-    PaintShadows(textStyle->mTextShadow, startOffset, maxLength,
-        aDirtyRect, framePt, textBaselinePt, snappedStartEdge, provider,
-        foregroundColor, clipEdges, ctx);
+    PaintShadows(
+        textStyle->mTextShadow, range, aDirtyRect, framePt, textBaselinePt,
+        snappedStartEdge, provider, foregroundColor, clipEdges, ctx);
   }
 
   gfxFloat advanceWidth;
-  DrawText(ctx, dirtyRect, framePt, textBaselinePt, startOffset, maxLength, provider,
+  DrawText(ctx, dirtyRect, framePt, textBaselinePt, range, provider,
            textPaintStyle, foregroundColor, clipEdges, advanceWidth,
            (GetStateBits() & TEXT_HYPHEN_BREAK) != 0,
            nullptr, aContextPaint, aCallbacks);
 }
 
 static void
 DrawTextRun(gfxTextRun* aTextRun,
             gfxContext* const aCtx,
             const gfxPoint& aTextBaselinePt,
-            uint32_t aOffset, uint32_t aLength,
+            gfxTextRun::Range aRange,
             PropertyProvider* aProvider,
             nscolor aTextColor,
             gfxFloat* aAdvanceWidth,
             gfxTextContextPaint* aContextPaint,
             nsTextFrame::DrawPathCallbacks* aCallbacks)
 {
   DrawMode drawMode = aCallbacks ? DrawMode::GLYPH_PATH :
                                    DrawMode::GLYPH_FILL;
   if (aCallbacks) {
     aCallbacks->NotifyBeforeText(aTextColor);
-    aTextRun->Draw(aCtx, aTextBaselinePt, drawMode, aOffset, aLength,
+    aTextRun->Draw(aCtx, aTextBaselinePt, drawMode, aRange,
                    aProvider, aAdvanceWidth, aContextPaint, aCallbacks);
     aCallbacks->NotifyAfterText();
   } else {
     aCtx->SetColor(Color::FromABGR(aTextColor));
-    aTextRun->Draw(aCtx, aTextBaselinePt, drawMode, aOffset, aLength,
+    aTextRun->Draw(aCtx, aTextBaselinePt, drawMode, aRange,
                    aProvider, aAdvanceWidth, aContextPaint);
   }
 }
 
 void
 nsTextFrame::DrawTextRun(gfxContext* const aCtx,
                          const gfxPoint& aTextBaselinePt,
-                         uint32_t aOffset, uint32_t aLength,
+                         Range aRange,
                          PropertyProvider& aProvider,
                          nscolor aTextColor,
                          gfxFloat& aAdvanceWidth,
                          bool aDrawSoftHyphen,
                          gfxTextContextPaint* aContextPaint,
                          nsTextFrame::DrawPathCallbacks* aCallbacks)
 {
-  ::DrawTextRun(mTextRun, aCtx, aTextBaselinePt, aOffset, aLength, &aProvider,
+  ::DrawTextRun(mTextRun, aCtx, aTextBaselinePt, aRange, &aProvider,
                 aTextColor, &aAdvanceWidth, aContextPaint, aCallbacks);
 
   if (aDrawSoftHyphen) {
     // Don't use ctx as the context, because we need a reference context here,
     // ctx may be transformed.
     nsAutoPtr<gfxTextRun> hyphenTextRun(GetHyphenTextRun(mTextRun, nullptr, this));
     if (hyphenTextRun.get()) {
       // For right-to-left text runs, the soft-hyphen is positioned at the left
       // of the text, minus its own width
-      gfxFloat hyphenBaselineX = aTextBaselinePt.x + mTextRun->GetDirection() * aAdvanceWidth -
+      gfxFloat hyphenBaselineX =
         (mTextRun->IsRightToLeft() ? hyphenTextRun->GetAdvanceWidth() : 0);
       ::DrawTextRun(hyphenTextRun.get(), aCtx,
                     gfxPoint(hyphenBaselineX, aTextBaselinePt.y),
-                    0, hyphenTextRun->GetLength(),
+                    Range(hyphenTextRun.get()),
                     nullptr, aTextColor, nullptr, aContextPaint, aCallbacks);
     }
   }
 }
 
 void
 nsTextFrame::DrawTextRunAndDecorations(
     gfxContext* const aCtx, const gfxRect& aDirtyRect,
     const gfxPoint& aFramePt, const gfxPoint& aTextBaselinePt,
-    uint32_t aOffset, uint32_t aLength,
+    Range aRange,
     PropertyProvider& aProvider,
     const nsTextPaintStyle& aTextStyle,
     nscolor aTextColor,
     const nsCharClipDisplayItem::ClipEdges& aClipEdges,
     gfxFloat& aAdvanceWidth,
     bool aDrawSoftHyphen,
     const TextDecorations& aDecorations,
     const nscolor* const aDecorationOverrideColor,
@@ -6744,21 +6739,21 @@ nsTextFrame::DrawTextRunAndDecorations(
         aDecorationOverrideColor, decPt, 0.0, decSize, ascent,
         decorationOffsetDir * metrics.maxAscent,
         NS_STYLE_TEXT_DECORATION_LINE_OVERLINE, dec.mStyle,
         eNormalDecoration, aCallbacks, verticalRun);
     }
 
     // CSS 2.1 mandates that text be painted after over/underlines, and *then*
     // line-throughs
-    DrawTextRun(aCtx, aTextBaselinePt, aOffset, aLength, aProvider, aTextColor,
+    DrawTextRun(aCtx, aTextBaselinePt, aRange, aProvider, aTextColor,
                 aAdvanceWidth, aDrawSoftHyphen, aContextPaint, aCallbacks);
 
     // Emphasis marks
-    DrawEmphasisMarks(aCtx, wm, aTextBaselinePt, aOffset, aLength,
+    DrawEmphasisMarks(aCtx, wm, aTextBaselinePt, aRange,
                       aDecorationOverrideColor, aProvider);
 
     // Line-throughs
     for (uint32_t i = aDecorations.mStrikes.Length(); i-- > 0; ) {
       const LineDecoration& dec = aDecorations.mStrikes[i];
       if (dec.mStyle == NS_STYLE_TEXT_DECORATION_STYLE_NONE) {
         continue;
       }
@@ -6779,17 +6774,17 @@ nsTextFrame::DrawTextRunAndDecorations(
         dec.mStyle, eNormalDecoration, aCallbacks, verticalRun);
     }
 }
 
 void
 nsTextFrame::DrawText(
     gfxContext* const aCtx, const gfxRect& aDirtyRect,
     const gfxPoint& aFramePt, const gfxPoint& aTextBaselinePt,
-    uint32_t aOffset, uint32_t aLength,
+    Range aRange,
     PropertyProvider& aProvider,
     const nsTextPaintStyle& aTextStyle,
     nscolor aTextColor,
     const nsCharClipDisplayItem::ClipEdges& aClipEdges,
     gfxFloat& aAdvanceWidth,
     bool aDrawSoftHyphen,
     const nscolor* const aDecorationOverrideColor,
     gfxTextContextPaint* aContextPaint,
@@ -6800,22 +6795,22 @@ nsTextFrame::DrawText(
                      aCallbacks ? eUnresolvedColors : eResolvedColors,
                      decorations);
 
   // Hide text decorations if we're currently hiding @font-face fallback text
   const bool drawDecorations = !aProvider.GetFontGroup()->ShouldSkipDrawing() &&
                                (decorations.HasDecorationLines() ||
                                 StyleText()->HasTextEmphasis());
   if (drawDecorations) {
-    DrawTextRunAndDecorations(aCtx, aDirtyRect, aFramePt, aTextBaselinePt, aOffset, aLength,
+    DrawTextRunAndDecorations(aCtx, aDirtyRect, aFramePt, aTextBaselinePt, aRange,
                               aProvider, aTextStyle, aTextColor, aClipEdges, aAdvanceWidth,
                               aDrawSoftHyphen, decorations,
                               aDecorationOverrideColor, aContextPaint, aCallbacks);
   } else {
-    DrawTextRun(aCtx, aTextBaselinePt, aOffset, aLength, aProvider,
+    DrawTextRun(aCtx, aTextBaselinePt, aRange, aProvider,
                 aTextColor, aAdvanceWidth, aDrawSoftHyphen, aContextPaint, aCallbacks);
   }
 }
 
 int16_t
 nsTextFrame::GetSelectionStatus(int16_t* aSelectionFlags)
 {
   // get the selection controller
@@ -6859,26 +6854,26 @@ nsTextFrame::IsVisibleInSelection(nsISel
   return found;
 }
 
 /**
  * Compute the longest prefix of text whose width is <= aWidth. Return
  * the length of the prefix. Also returns the width of the prefix in aFitWidth.
  */
 static uint32_t
-CountCharsFit(gfxTextRun* aTextRun, uint32_t aStart, uint32_t aLength,
+CountCharsFit(gfxTextRun* aTextRun, gfxTextRun::Range aRange,
               gfxFloat aWidth, PropertyProvider* aProvider,
               gfxFloat* aFitWidth)
 {
   uint32_t last = 0;
   gfxFloat width = 0;
-  for (uint32_t i = 1; i <= aLength; ++i) {
-    if (i == aLength || aTextRun->IsClusterStart(aStart + i)) {
-      gfxFloat nextWidth = width +
-          aTextRun->GetAdvanceWidth(aStart + last, i - last, aProvider);
+  for (uint32_t i = 1; i <= aRange.Length(); ++i) {
+    if (i == aRange.Length() || aTextRun->IsClusterStart(aRange.start + i)) {
+      gfxTextRun::Range range(aRange.start + last, aRange.start + i);
+      gfxFloat nextWidth = width + aTextRun->GetAdvanceWidth(range, aProvider);
       if (nextWidth > aWidth)
         break;
       last = i;
       width = nextWidth;
     }
   }
   *aFitWidth = width;
   return last;
@@ -6908,23 +6903,23 @@ nsTextFrame::GetCharacterOffsetAtFramePo
 
   PropertyProvider provider(this, iter, nsTextFrame::eInflated);
   // Trim leading but not trailing whitespace if possible
   provider.InitializeForDisplay(false);
   gfxFloat width = mTextRun->IsVertical()
     ? (mTextRun->IsInlineReversed() ? mRect.height - aPoint.y : aPoint.y)
     : (mTextRun->IsInlineReversed() ? mRect.width - aPoint.x : aPoint.x);
   gfxFloat fitWidth;
-  uint32_t skippedLength = ComputeTransformedLength(provider);
-
-  uint32_t charsFit = CountCharsFit(mTextRun,
-      provider.GetStart().GetSkippedOffset(), skippedLength, width, &provider, &fitWidth);
+  Range skippedRange = ComputeTransformedRange(provider);
+
+  uint32_t charsFit = CountCharsFit(mTextRun, skippedRange,
+                                    width, &provider, &fitWidth);
 
   int32_t selectedOffset;
-  if (charsFit < skippedLength) {
+  if (charsFit < skippedRange.Length()) {
     // charsFit characters fitted, but no more could fit. See if we're
     // more than halfway through the cluster.. If we are, choose the next
     // cluster.
     gfxSkipCharsIterator extraCluster(provider.GetStart());
     extraCluster.AdvanceSkipped(charsFit);
 
     bool allowSplitLigature = true; // Allow selection of partial ligature...
 
@@ -6938,30 +6933,31 @@ nsTextFrame::GetCharacterOffsetAtFramePo
         gfxFontUtils::IsRegionalIndicator
           (SURROGATE_TO_UCS4(frag->CharAt(offs), frag->CharAt(offs + 1)))) {
       allowSplitLigature = false;
       if (extraCluster.GetSkippedOffset() > 1 &&
           !mTextRun->IsLigatureGroupStart(extraCluster.GetSkippedOffset())) {
         // CountCharsFit() left us in the middle of the flag; back up over the
         // first character of the ligature, and adjust fitWidth accordingly.
         extraCluster.AdvanceSkipped(-2); // it's a surrogate pair: 2 code units
-        fitWidth -= mTextRun->GetAdvanceWidth(extraCluster.GetSkippedOffset(),
-                                              2, &provider);
+        fitWidth -= mTextRun->GetAdvanceWidth(
+          Range(extraCluster.GetSkippedOffset(),
+                extraCluster.GetSkippedOffset() + 2), &provider);
       }
     }
 
     gfxSkipCharsIterator extraClusterLastChar(extraCluster);
     FindClusterEnd(mTextRun,
                    provider.GetStart().GetOriginalOffset() + provider.GetOriginalLength(),
                    &extraClusterLastChar, allowSplitLigature);
     PropertyProvider::Spacing spacing;
+    Range extraClusterRange(extraCluster.GetSkippedOffset(),
+                            extraClusterLastChar.GetSkippedOffset() + 1);
     gfxFloat charWidth =
-        mTextRun->GetAdvanceWidth(extraCluster.GetSkippedOffset(),
-                                  GetSkippedDistance(extraCluster, extraClusterLastChar) + 1,
-                                  &provider, &spacing);
+        mTextRun->GetAdvanceWidth(extraClusterRange, &provider, &spacing);
     charWidth -= spacing.mBefore + spacing.mAfter;
     selectedOffset = !aForInsertionPoint ||
       width <= fitWidth + spacing.mBefore + charWidth/2
         ? extraCluster.GetOriginalOffset()
         : extraClusterLastChar.GetOriginalOffset() + 1;
   } else {
     // All characters fitted, we're at (or beyond) the end of the text.
     // XXX This could be some pathological situation where negative spacing
@@ -7149,20 +7145,19 @@ nsTextFrame::GetPointFromOffset(int32_t 
 
   if (inOffset < trimmedEnd &&
       !iter.IsOriginalCharSkipped() &&
       !mTextRun->IsClusterStart(iter.GetSkippedOffset())) {
     NS_WARNING("GetPointFromOffset called for non-cluster boundary");
     FindClusterStart(mTextRun, trimmedOffset, &iter);
   }
 
-  gfxFloat advance =
-    mTextRun->GetAdvanceWidth(properties.GetStart().GetSkippedOffset(),
-                              GetSkippedDistance(properties.GetStart(), iter),
-                              &properties);
+  Range range(properties.GetStart().GetSkippedOffset(),
+              iter.GetSkippedOffset());
+  gfxFloat advance = mTextRun->GetAdvanceWidth(range, &properties);
   nscoord iSize = NSToCoordCeilClamped(advance);
 
   if (mTextRun->IsVertical()) {
     if (mTextRun->IsInlineReversed()) {
       outPoint->y = mRect.height - iSize;
     } else {
       outPoint->y = iSize;
     }
@@ -7854,17 +7849,17 @@ nsTextFrame::AddInlineMinISizeForFlow(ns
     FindStartAfterSkippingWhitespace(&provider, aData, textStyle, &iter, flowEndInTextRun);
 
   AutoTArray<bool,BIG_TEXT_NODE_SIZE> hyphBuffer;
   bool *hyphBreakBefore = nullptr;
   if (hyphenating) {
     hyphBreakBefore = hyphBuffer.AppendElements(flowEndInTextRun - start,
                                                 fallible);
     if (hyphBreakBefore) {
-      provider.GetHyphenationBreaks(start, flowEndInTextRun - start,
+      provider.GetHyphenationBreaks(Range(start, flowEndInTextRun),
                                     hyphBreakBefore);
     }
   }
 
   for (uint32_t i = start, wordStart = start; i <= flowEndInTextRun; ++i) {
     bool preformattedNewline = false;
     bool preformattedTab = false;
     if (i < flowEndInTextRun) {
@@ -7879,45 +7874,42 @@ nsTextFrame::AddInlineMinISizeForFlow(ns
           (!hyphBreakBefore || !hyphBreakBefore[i - start]))
       {
         // we can't break here (and it's not the end of the flow)
         continue;
       }
     }
 
     if (i > wordStart) {
-      nscoord width =
-        NSToCoordCeilClamped(textRun->GetAdvanceWidth(wordStart, i - wordStart,
-                                                      &provider));
+      nscoord width = NSToCoordCeilClamped(
+        textRun->GetAdvanceWidth(Range(wordStart, i), &provider));
       width = std::max(0, width);
       aData->currentLine = NSCoordSaturatingAdd(aData->currentLine, width);
       aData->atStartOfLine = false;
 
       if (collapseWhitespace) {
         uint32_t trimStart = GetEndOfTrimmedText(frag, textStyle, wordStart, i, &iter);
         if (trimStart == start) {
           // This is *all* trimmable whitespace, so whatever trailingWhitespace
           // we saw previously is still trailing...
           aData->trailingWhitespace += width;
         } else {
           // Some non-whitespace so the old trailingWhitespace is no longer trailing
-          nscoord wsWidth =
-            NSToCoordCeilClamped(textRun->GetAdvanceWidth(trimStart,
-                                                          i - trimStart,
-                                                          &provider));
+          nscoord wsWidth = NSToCoordCeilClamped(
+            textRun->GetAdvanceWidth(Range(trimStart, i), &provider));
           aData->trailingWhitespace = std::max(0, wsWidth);
         }
       } else {
         aData->trailingWhitespace = 0;
       }
     }
 
     if (preformattedTab) {
       PropertyProvider::Spacing spacing;
-      provider.GetSpacing(i, 1, &spacing);
+      provider.GetSpacing(Range(i, i + 1), &spacing);
       aData->currentLine += nscoord(spacing.mBefore);
       gfxFloat afterTab =
         AdvanceToNextTab(aData->currentLine, this,
                          textRun, &tabWidth);
       aData->currentLine = nscoord(afterTab + spacing.mAfter);
       wordStart = i + 1;
     } else if (i < flowEndInTextRun ||
         (i == textRun->GetLength() &&
@@ -8035,44 +8027,41 @@ nsTextFrame::AddInlinePrefISizeForFlow(n
       preformattedTab = preformatTabs && textRun->CharIsTab(i);
       if (!preformattedNewline && !preformattedTab) {
         // we needn't break here (and it's not the end of the flow)
         continue;
       }
     }
 
     if (i > lineStart) {
-      nscoord width =
-        NSToCoordCeilClamped(textRun->GetAdvanceWidth(lineStart, i - lineStart,
-                                                      &provider));
+      nscoord width = NSToCoordCeilClamped(
+        textRun->GetAdvanceWidth(Range(lineStart, i), &provider));
       width = std::max(0, width);
       aData->currentLine = NSCoordSaturatingAdd(aData->currentLine, width);
 
       if (collapseWhitespace) {
         uint32_t trimStart = GetEndOfTrimmedText(frag, textStyle, lineStart, i, &iter);
         if (trimStart == start) {
           // This is *all* trimmable whitespace, so whatever trailingWhitespace
           // we saw previously is still trailing...
           aData->trailingWhitespace += width;
         } else {
           // Some non-whitespace so the old trailingWhitespace is no longer trailing
-          nscoord wsWidth =
-            NSToCoordCeilClamped(textRun->GetAdvanceWidth(trimStart,
-                                                          i - trimStart,
-                                                          &provider));
+          nscoord wsWidth = NSToCoordCeilClamped(
+            textRun->GetAdvanceWidth(Range(trimStart, i), &provider));
           aData->trailingWhitespace = std::max(0, wsWidth);
         }
       } else {
         aData->trailingWhitespace = 0;
       }
     }
 
     if (preformattedTab) {
       PropertyProvider::Spacing spacing;
-      provider.GetSpacing(i, 1, &spacing);
+      provider.GetSpacing(Range(i, i + 1), &spacing);
       aData->currentLine += nscoord(spacing.mBefore);
       gfxFloat afterTab =
         AdvanceToNextTab(aData->currentLine, this,
                          textRun, &tabWidth);
       aData->currentLine = nscoord(afterTab + spacing.mAfter);
       lineStart = i + 1;
     } else if (preformattedNewline) {
       aData->ForceBreak();
@@ -8170,18 +8159,17 @@ nsTextFrame::ComputeTightBounds(DrawTarg
     return nsRect(0, 0, 0, 0);
 
   PropertyProvider provider(const_cast<nsTextFrame*>(this), iter,
                             nsTextFrame::eInflated);
   // Trim trailing whitespace
   provider.InitializeForDisplay(true);
 
   gfxTextRun::Metrics metrics =
-        mTextRun->MeasureText(provider.GetStart().GetSkippedOffset(),
-                              ComputeTransformedLength(provider),
+        mTextRun->MeasureText(ComputeTransformedRange(provider),
                               gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS,
                               aDrawTarget, &provider);
   if (GetWritingMode().IsLineInverted()) {
     metrics.mBoundingBox.y = -metrics.mBoundingBox.YMost();
   }
   // mAscent should be the same as metrics.mAscent, but it's what we use to
   // paint so that's the one we'll use.
   nsRect boundingBox = RoundOut(metrics.mBoundingBox);
@@ -8204,18 +8192,17 @@ nsTextFrame::GetPrefWidthTightBounds(nsR
   if (!mTextRun)
     return NS_ERROR_FAILURE;
 
   PropertyProvider provider(const_cast<nsTextFrame*>(this), iter,
                             nsTextFrame::eInflated);
   provider.InitializeForMeasure();
 
   gfxTextRun::Metrics metrics =
-        mTextRun->MeasureText(provider.GetStart().GetSkippedOffset(),
-                              ComputeTransformedLength(provider),
+        mTextRun->MeasureText(ComputeTransformedRange(provider),
                               gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS,
                               aContext->GetDrawTarget(), &provider);
   // Round it like nsTextFrame::ComputeTightBounds() to ensure consistency.
   *aX = NSToCoordFloor(metrics.mBoundingBox.x);
   *aXMost = NSToCoordCeil(metrics.mBoundingBox.XMost());
 
   return NS_OK;
 }
@@ -9026,17 +9013,18 @@ nsTextFrame::ReflowText(nsLineLayout& aL
 
   // Compute space and letter counts for justification, if required
   if (!textStyle->WhiteSpaceIsSignificant() &&
       (lineContainer->StyleText()->mTextAlign == NS_STYLE_TEXT_ALIGN_JUSTIFY ||
        lineContainer->StyleText()->mTextAlignLast == NS_STYLE_TEXT_ALIGN_JUSTIFY ||
        shouldSuppressLineBreak) &&
       !lineContainer->IsSVGText()) {
     AddStateBits(TEXT_JUSTIFICATION_ENABLED);
-    provider.ComputeJustification(offset, charsFit);
+    provider.ComputeJustification(Range(uint32_t(offset),
+                                        uint32_t(offset + charsFit)));
     aLineLayout.SetJustificationInfo(provider.GetJustificationInfo());
   }
 
   SetLength(contentLength, &aLineLayout, ALLOW_FRAME_CREATION_AND_DESTRUCTION);
 
   InvalidateFrame();
 
 #ifdef NOISY_REFLOW
@@ -9084,23 +9072,24 @@ nsTextFrame::TrimTrailingWhiteSpace(Draw
       trimmed.GetEnd() < GetContentEnd()) {
     gfxSkipCharsIterator end = trimmedEndIter;
     uint32_t endOffset = end.ConvertOriginalToSkipped(GetContentOffset() + contentLength);
     if (trimmedEnd < endOffset) {
       // We can't be dealing with tabs here ... they wouldn't be trimmed. So it's
       // OK to pass null for the line container.
       PropertyProvider provider(mTextRun, textStyle, frag, this, start, contentLength,
                                 nullptr, 0, nsTextFrame::eInflated);
-      delta = mTextRun->GetAdvanceWidth(trimmedEnd, endOffset - trimmedEnd, &provider);
+      delta = mTextRun->
+        GetAdvanceWidth(Range(trimmedEnd, endOffset), &provider);
       result.mChanged = true;
     }
   }
 
   gfxFloat advanceDelta;
-  mTextRun->SetLineBreaks(trimmedStart, trimmedEnd - trimmedStart,
+  mTextRun->SetLineBreaks(Range(trimmedStart, trimmedEnd),
                           (GetStateBits() & TEXT_START_OF_LINE) != 0, true,
                           &advanceDelta);
   if (advanceDelta != 0) {
     result.mChanged = true;
   }
 
   // aDeltaWidth is *subtracted* from our width.
   // If advanceDelta is positive then setting the line break made us longer,
@@ -9140,18 +9129,17 @@ nsTextFrame::RecomputeOverflow(nsIFrame*
   if (!mTextRun)
     return result;
 
   PropertyProvider provider(this, iter, nsTextFrame::eInflated);
   // Don't trim trailing space, in case we need to paint it as selected.
   provider.InitializeForDisplay(false);
 
   gfxTextRun::Metrics textMetrics =
-    mTextRun->MeasureText(provider.GetStart().GetSkippedOffset(),
-                          ComputeTransformedLength(provider),
+    mTextRun->MeasureText(ComputeTransformedRange(provider),
                           gfxFont::LOOSE_INK_EXTENTS, nullptr,
                           &provider);
   if (GetWritingMode().IsLineInverted()) {
     textMetrics.mBoundingBox.y = -textMetrics.mBoundingBox.YMost();
   }
   nsRect boundingBox = RoundOut(textMetrics.mBoundingBox);
   boundingBox += nsPoint(0, mAscent);
   if (mTextRun->IsVertical()) {
--- a/layout/generic/nsTextFrame.h
+++ b/layout/generic/nsTextFrame.h
@@ -40,16 +40,17 @@ public:
 };
 
 class nsTextFrame : public nsTextFrameBase {
   typedef mozilla::TextRangeStyle TextRangeStyle;
   typedef mozilla::gfx::DrawTarget DrawTarget;
   typedef mozilla::gfx::Point Point;
   typedef mozilla::gfx::Rect Rect;
   typedef mozilla::gfx::Size Size;
+  typedef gfxTextRun::Range Range;
 
 public:
   NS_DECL_QUERYFRAME_TARGET(nsTextFrame)
   NS_DECL_FRAMEARENA_HELPERS
 
   friend class nsContinuingTextFrame;
   friend class nsDisplayTextGeometry;
   friend class nsDisplayText;
@@ -401,56 +402,53 @@ public:
   // helper: paint text frame when we're impacted by at least one selection.
   // Return false if the text was not painted and we should continue with
   // the fast path.
   bool PaintTextWithSelection(gfxContext* aCtx,
                               const gfxPoint& aFramePt,
                               const gfxPoint& aTextBaselinePt,
                               const gfxRect& aDirtyRect,
                               PropertyProvider& aProvider,
-                              uint32_t aContentOffset,
-                              uint32_t aContentLength,
+                              Range aRange,
                               nsTextPaintStyle& aTextPaintStyle,
                               const nsCharClipDisplayItem::ClipEdges& aClipEdges,
                               gfxTextContextPaint* aContextPaint,
                               DrawPathCallbacks* aCallbacks);
   // helper: paint text with foreground and background colors determined
   // by selection(s). Also computes a mask of all selection types applying to
   // our text, returned in aAllTypes.
   // Return false if the text was not painted and we should continue with
   // the fast path.
   bool PaintTextWithSelectionColors(gfxContext* aCtx,
                                     const gfxPoint& aFramePt,
                                     const gfxPoint& aTextBaselinePt,
                                     const gfxRect& aDirtyRect,
                                     PropertyProvider& aProvider,
-                                    uint32_t aContentOffset,
-                                    uint32_t aContentLength,
+                                    Range aContentRange,
                                     nsTextPaintStyle& aTextPaintStyle,
                                     SelectionDetails* aDetails,
                                     SelectionType* aAllTypes,
                              const nsCharClipDisplayItem::ClipEdges& aClipEdges,
                                     DrawPathCallbacks* aCallbacks);
   // helper: paint text decorations for text selected by aSelectionType
   void PaintTextSelectionDecorations(gfxContext* aCtx,
                                      const gfxPoint& aFramePt,
                                      const gfxPoint& aTextBaselinePt,
                                      const gfxRect& aDirtyRect,
                                      PropertyProvider& aProvider,
-                                     uint32_t aContentOffset,
-                                     uint32_t aContentLength,
+                                     Range aContentRange,
                                      nsTextPaintStyle& aTextPaintStyle,
                                      SelectionDetails* aDetails,
                                      SelectionType aSelectionType,
                                      DrawPathCallbacks* aCallbacks);
 
   void DrawEmphasisMarks(gfxContext* aContext,
                          mozilla::WritingMode aWM,
                          const gfxPoint& aTextBaselinePt,
-                         uint32_t aOffset, uint32_t aLength,
+                         Range aRange,
                          const nscolor* aDecorationOverrideColor,
                          PropertyProvider& aProvider);
 
   virtual nscolor GetCaretColorAt(int32_t aOffset) override;
 
   int16_t GetSelectionStatus(int16_t* aSelectionFlags);
 
   int32_t GetContentOffset() const { return mContentOffset; }
@@ -593,32 +591,31 @@ protected:
                                nsRect* aVisualOverflowRect,
                                bool aIncludeTextDecorations);
 
   // Update information of emphasis marks, and return the visial
   // overflow rect of the emphasis marks.
   nsRect UpdateTextEmphasis(mozilla::WritingMode aWM,
                             PropertyProvider& aProvider);
 
-  void PaintOneShadow(uint32_t aOffset,
-                      uint32_t aLength,
+  void PaintOneShadow(Range aRange,
                       nsCSSShadowItem* aShadowDetails,
                       PropertyProvider* aProvider,
                       const nsRect& aDirtyRect,
                       const gfxPoint& aFramePt,
                       const gfxPoint& aTextBaselinePt,
                       gfxContext* aCtx,
                       const nscolor& aForegroundColor,
                       const nsCharClipDisplayItem::ClipEdges& aClipEdges,
                       nscoord aLeftSideOffset,
                       gfxRect& aBoundingBox,
                       uint32_t aBlurFlags);
 
   void PaintShadows(nsCSSShadowArray* aShadow,
-                    uint32_t aOffset, uint32_t aLength,
+                    Range aRange,
                     const nsRect& aDirtyRect,
                     const gfxPoint& aFramePt,
                     const gfxPoint& aTextBaselinePt,
                     nscoord aLeftEdgeOffset,
                     PropertyProvider& aProvider,
                     nscolor aForegroundColor,
                     const nsCharClipDisplayItem::ClipEdges& aClipEdges,
                     gfxContext* aCtx);
@@ -694,48 +691,45 @@ protected:
     eUnresolvedColors
   };
   void GetTextDecorations(nsPresContext* aPresContext,
                           TextDecorationColorResolution aColorResolution,
                           TextDecorations& aDecorations);
 
   void DrawTextRun(gfxContext* const aCtx,
                    const gfxPoint& aTextBaselinePt,
-                   uint32_t aOffset,
-                   uint32_t aLength,
+                   Range aRange,
                    PropertyProvider& aProvider,
                    nscolor aTextColor,
                    gfxFloat& aAdvanceWidth,
                    bool aDrawSoftHyphen,
                    gfxTextContextPaint* aContextPaint,
                    DrawPathCallbacks* aCallbacks);
 
   void DrawTextRunAndDecorations(gfxContext* const aCtx,
                                  const gfxRect& aDirtyRect,
                                  const gfxPoint& aFramePt,
                                  const gfxPoint& aTextBaselinePt,
-                                 uint32_t aOffset,
-                                 uint32_t aLength,
+                                 Range aRange,
                                  PropertyProvider& aProvider,
                                  const nsTextPaintStyle& aTextStyle,
                                  nscolor aTextColor,
                              const nsCharClipDisplayItem::ClipEdges& aClipEdges,
                                  gfxFloat& aAdvanceWidth,
                                  bool aDrawSoftHyphen,
                                  const TextDecorations& aDecorations,
                                  const nscolor* const aDecorationOverrideColor,
                                  gfxTextContextPaint* aContextPaint,
                                  DrawPathCallbacks* aCallbacks);
 
   void DrawText(gfxContext* const aCtx,
                 const gfxRect& aDirtyRect,
                 const gfxPoint& aFramePt,
                 const gfxPoint& aTextBaselinePt,
-                uint32_t aOffset,
-                uint32_t aLength,
+                Range aRange,
                 PropertyProvider& aProvider,
                 const nsTextPaintStyle& aTextStyle,
                 nscolor aTextColor,
                 const nsCharClipDisplayItem::ClipEdges& aClipEdges,
                 gfxFloat& aAdvanceWidth,
                 bool aDrawSoftHyphen,
                 const nscolor* const aDecorationOverrideColor = nullptr,
                 gfxTextContextPaint* aContextPaint = nullptr,
--- a/layout/generic/nsTextRunTransformations.cpp
+++ b/layout/generic/nsTextRunTransformations.cpp
@@ -64,21 +64,21 @@ nsTransformedTextRun::SetCapitalization(
       return;
     memset(mCapitalize.Elements(), 0, GetLength()*sizeof(bool));
   }
   memcpy(mCapitalize.Elements() + aStart, aCapitalization, aLength*sizeof(bool));
   mNeedsRebuild = true;
 }
 
 bool
-nsTransformedTextRun::SetPotentialLineBreaks(uint32_t aStart, uint32_t aLength,
+nsTransformedTextRun::SetPotentialLineBreaks(Range aRange,
                                              uint8_t* aBreakBefore)
 {
   bool changed =
-    gfxTextRun::SetPotentialLineBreaks(aStart, aLength, aBreakBefore);
+    gfxTextRun::SetPotentialLineBreaks(aRange, aBreakBefore);
   if (changed) {
     mNeedsRebuild = true;
   }
   return changed;
 }
 
 size_t
 nsTransformedTextRun::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf)
@@ -126,17 +126,17 @@ nsTransformingTextRunFactory::MakeTextRu
 }
 
 void
 MergeCharactersInTextRun(gfxTextRun* aDest, gfxTextRun* aSrc,
                          const bool* aCharsToMerge, const bool* aDeletedChars)
 {
   aDest->ResetGlyphRuns();
 
-  gfxTextRun::GlyphRunIterator iter(aSrc, 0, aSrc->GetLength());
+  gfxTextRun::GlyphRunIterator iter(aSrc, gfxTextRun::Range(aSrc));
   uint32_t offset = 0;
   AutoTArray<gfxTextRun::DetailedGlyph,2> glyphs;
   while (iter.NextRun()) {
     gfxTextRun::GlyphRun* run = iter.GetGlyphRun();
     nsresult rv = aDest->AddGlyphRun(run->mFont, run->mMatchType,
                                      offset, false, run->mOrientation);
     if (NS_FAILED(rv))
       return;
@@ -641,18 +641,18 @@ nsCaseTransformTextRunFactory::RebuildTe
     child = cachedChild.get();
   }
   if (!child)
     return;
   // Copy potential linebreaks into child so they're preserved
   // (and also child will be shaped appropriately)
   NS_ASSERTION(convertedString.Length() == canBreakBeforeArray.Length(),
                "Dropped characters or break-before values somewhere!");
-  child->SetPotentialLineBreaks(0, canBreakBeforeArray.Length(),
-                                canBreakBeforeArray.Elements());
+  gfxTextRun::Range range(0, uint32_t(canBreakBeforeArray.Length()));
+  child->SetPotentialLineBreaks(range, canBreakBeforeArray.Elements());
   if (transformedChild) {
     transformedChild->FinishSettingProperties(aRefDrawTarget, aMFR);
   }
 
   if (mergeNeeded) {
     // Now merge multiple characters into one multi-glyph character as required
     // and deal with skipping deleted accent chars
     NS_ASSERTION(charsToMergeArray.Length() == child->GetLength(),
@@ -661,11 +661,11 @@ nsCaseTransformTextRunFactory::RebuildTe
                  "destination length mismatch");
     MergeCharactersInTextRun(aTextRun, child, charsToMergeArray.Elements(),
                              deletedCharsArray.Elements());
   } else {
     // No merging to do, so just copy; this produces a more optimized textrun.
     // We can't steal the data because the child may be cached and stealing
     // the data would break the cache.
     aTextRun->ResetGlyphRuns();
-    aTextRun->CopyGlyphDataFrom(child, 0, child->GetLength(), 0);
+    aTextRun->CopyGlyphDataFrom(child, gfxTextRun::Range(child), 0);
   }
 }
--- a/layout/generic/nsTextRunTransformations.h
+++ b/layout/generic/nsTextRunTransformations.h
@@ -125,18 +125,17 @@ public:
   ~nsTransformedTextRun() {
     if (mOwnsFactory) {
       delete mFactory;
     }
   }
   
   void SetCapitalization(uint32_t aStart, uint32_t aLength,
                          bool* aCapitalization);
-  virtual bool SetPotentialLineBreaks(uint32_t aStart, uint32_t aLength,
-                                      uint8_t* aBreakBefore);
+  virtual bool SetPotentialLineBreaks(Range aRange, uint8_t* aBreakBefore);
   /**
    * Called after SetCapitalization and SetPotentialLineBreaks
    * are done and before we request any data from the textrun. Also always
    * called after a Create.
    */
   void FinishSettingProperties(mozilla::gfx::DrawTarget* aRefDrawTarget,
                                gfxMissingFontRecorder* aMFR)
   {
--- a/layout/inspector/nsFontFaceList.cpp
+++ b/layout/inspector/nsFontFaceList.cpp
@@ -55,17 +55,18 @@ nsFontFaceList::GetLength(uint32_t *aLen
 
 ////////////////////////////////////////////////////////////////////////
 // nsFontFaceList
 
 nsresult
 nsFontFaceList::AddFontsFromTextRun(gfxTextRun* aTextRun,
                                     uint32_t aOffset, uint32_t aLength)
 {
-  gfxTextRun::GlyphRunIterator iter(aTextRun, aOffset, aLength);
+  gfxTextRun::Range range(aOffset, aOffset + aLength);
+  gfxTextRun::GlyphRunIterator iter(aTextRun, range);
   while (iter.NextRun()) {
     gfxFontEntry *fe = iter.GetGlyphRun()->mFont->GetFontEntry();
     // if we have already listed this face, just make sure the match type is
     // recorded
     nsFontFace* existingFace =
       static_cast<nsFontFace*>(mFontFaces.GetWeak(fe));
     if (existingFace) {
       existingFace->AddMatchType(iter.GetGlyphRun()->mMatchType);
--- a/layout/mathml/nsMathMLChar.cpp
+++ b/layout/mathml/nsMathMLChar.cpp
@@ -2142,17 +2142,17 @@ nsMathMLChar::PaintForeground(nsPresCont
   switch(mDraw)
   {
     case DRAW_NORMAL:
     case DRAW_VARIANT:
       // draw a single glyph (base size or size variant)
       // XXXfredw verify if mGlyphs[0] is non-null to workaround bug 973322.
       if (mGlyphs[0]) {
         mGlyphs[0]->Draw(thebesContext, gfxPoint(0.0, mUnscaledAscent),
-                         DrawMode::GLYPH_FILL, 0, mGlyphs[0]->GetLength(),
+                         DrawMode::GLYPH_FILL, Range(mGlyphs[0]),
                          nullptr, nullptr, nullptr);
       }
       break;
     case DRAW_PARTS: {
       // paint by parts
       if (NS_STRETCH_DIRECTION_VERTICAL == mDirection)
         PaintVertically(aPresContext, thebesContext, r, fgColor);
       else if (NS_STRETCH_DIRECTION_HORIZONTAL == mDirection)
@@ -2299,17 +2299,17 @@ nsMathMLChar::PaintVertically(nsPresCont
         else { // middle
           clipRect.y = start[i];
           clipRect.height = end[i] - start[i];
         }
       }
       if (!clipRect.IsEmpty()) {
         AutoPushClipRect clip(aThebesContext, oneDevPixel, clipRect);
         mGlyphs[i]->Draw(aThebesContext, gfxPoint(dx, dy),
-                         DrawMode::GLYPH_FILL, 0, mGlyphs[i]->GetLength(),
+                         DrawMode::GLYPH_FILL, Range(mGlyphs[i]),
                          nullptr, nullptr, nullptr);
       }
     }
   }
 
   ///////////////
   // fill the gap between top and middle, and between middle and bottom.
   if (!mGlyphs[3]) { // null glue : draw a rule
@@ -2367,17 +2367,17 @@ nsMathMLChar::PaintVertically(nsPresCont
       nscoord dy = std::max(end[i], aRect.y);
       nscoord fillEnd = std::min(start[i+1], aRect.YMost());
       while (dy < fillEnd) {
         clipRect.y = dy;
         clipRect.height = std::min(bm.ascent + bm.descent, fillEnd - dy);
         AutoPushClipRect clip(aThebesContext, oneDevPixel, clipRect);
         dy += bm.ascent;
         mGlyphs[3]->Draw(aThebesContext, gfxPoint(dx, dy),
-                            DrawMode::GLYPH_FILL, 0, mGlyphs[3]->GetLength(),
+                            DrawMode::GLYPH_FILL, Range(mGlyphs[3]),
                             nullptr, nullptr, nullptr);
         dy += bm.descent;
       }
     }
   }
 #ifdef DEBUG
   else {
     for (i = 0; i < 2; ++i) {
@@ -2471,17 +2471,17 @@ nsMathMLChar::PaintHorizontally(nsPresCo
         else { // middle
           clipRect.x = start[i];
           clipRect.width = end[i] - start[i];
         }
       }
       if (!clipRect.IsEmpty()) {
         AutoPushClipRect clip(aThebesContext, oneDevPixel, clipRect);
         mGlyphs[i]->Draw(aThebesContext, gfxPoint(dx, dy),
-                         DrawMode::GLYPH_FILL, 0, mGlyphs[i]->GetLength(),
+                         DrawMode::GLYPH_FILL, Range(mGlyphs[i]),
                          nullptr, nullptr, nullptr);
       }
     }
   }
 
   ////////////////
   // fill the gap between left and middle, and between middle and right.
   if (!mGlyphs[3]) { // null glue : draw a rule
@@ -2537,17 +2537,17 @@ nsMathMLChar::PaintHorizontally(nsPresCo
       nscoord dx = std::max(end[i], aRect.x);
       nscoord fillEnd = std::min(start[i+1], aRect.XMost());
       while (dx < fillEnd) {
         clipRect.x = dx;
         clipRect.width = std::min(bm.rightBearing - bm.leftBearing, fillEnd - dx);
         AutoPushClipRect clip(aThebesContext, oneDevPixel, clipRect);
         dx -= bm.leftBearing;
         mGlyphs[3]->Draw(aThebesContext, gfxPoint(dx, dy),
-                            DrawMode::GLYPH_FILL, 0, mGlyphs[3]->GetLength(),
+                            DrawMode::GLYPH_FILL, Range(mGlyphs[3]),
                             nullptr, nullptr, nullptr);
         dx += bm.rightBearing;
       }
     }
   }
 #ifdef DEBUG
   else { // no glue
     for (i = 0; i < 2; ++i) {
--- a/layout/mathml/nsMathMLChar.h
+++ b/layout/mathml/nsMathMLChar.h
@@ -8,17 +8,17 @@
 
 #include "nsAutoPtr.h"
 #include "nsColor.h"
 #include "nsMathMLOperators.h"
 #include "nsPoint.h"
 #include "nsRect.h"
 #include "nsString.h"
 #include "nsBoundingMetrics.h"
-#include "gfxFont.h"
+#include "gfxTextRun.h"
 
 class nsGlyphTable;
 class nsIFrame;
 class nsDisplayListBuilder;
 class nsDisplayListSet;
 class nsPresContext;
 class nsRenderingContext;
 struct nsBoundingMetrics;
@@ -80,16 +80,17 @@ struct nsGlyphCode {
   }
 };
 
 // Class used to handle stretchy symbols (accent, delimiter and boundary
 // symbols).
 class nsMathMLChar
 {
 public:
+  typedef gfxTextRun::Range Range;
   typedef mozilla::gfx::DrawTarget DrawTarget;
 
   // constructor and destructor
   nsMathMLChar() {
     MOZ_COUNT_CTOR(nsMathMLChar);
     mStyleContext = nullptr;
     mUnscaledAscent = 0;
     mScaleX = mScaleY = 1.0;
--- a/layout/svg/SVGTextFrame.cpp
+++ b/layout/svg/SVGTextFrame.cpp
@@ -55,44 +55,26 @@ using namespace mozilla::gfx;
 // ============================================================================
 // Utility functions
 
 /**
  * Using the specified gfxSkipCharsIterator, converts an offset and length
  * in original char indexes to skipped char indexes.
  *
  * @param aIterator The gfxSkipCharsIterator to use for the conversion.
- * @param aOriginalOffset The original offset (input).
- * @param aOriginalLength The original length (input).
- * @param aSkippedOffset The skipped offset (output).
- * @param aSkippedLength The skipped length (output).
+ * @param aOriginalOffset The original offset.
+ * @param aOriginalLength The original length.
  */
-static void
+static gfxTextRun::Range
 ConvertOriginalToSkipped(gfxSkipCharsIterator& aIterator,
-                         uint32_t aOriginalOffset, uint32_t aOriginalLength,
-                         uint32_t& aSkippedOffset, uint32_t& aSkippedLength)
+                         uint32_t aOriginalOffset, uint32_t aOriginalLength)
 {
-  aSkippedOffset = aIterator.ConvertOriginalToSkipped(aOriginalOffset);
+  uint32_t start = aIterator.ConvertOriginalToSkipped(aOriginalOffset);
   aIterator.AdvanceOriginal(aOriginalLength);
-  aSkippedLength = aIterator.GetSkippedOffset() - aSkippedOffset;
-}
-
-/**
- * Using the specified gfxSkipCharsIterator, converts an offset and length
- * in original char indexes to skipped char indexes in place.
- *
- * @param aIterator The gfxSkipCharsIterator to use for the conversion.
- * @param aOriginalOffset The offset to convert from original to skipped.
- * @param aOriginalLength The length to convert from original to skipped.
- */
-static void
-ConvertOriginalToSkipped(gfxSkipCharsIterator& aIterator,
-                         uint32_t& aOffset, uint32_t& aLength)
-{
-  ConvertOriginalToSkipped(aIterator, aOffset, aLength, aOffset, aLength);
+  return gfxTextRun::Range(start, aIterator.GetSkippedOffset());
 }
 
 /**
  * Converts an nsPoint from app units to user space units using the specified
  * nsPresContext and returns it as a gfxPoint.
  */
 static gfxPoint
 AppUnitsToGfxUnits(const nsPoint& aPoint, const nsPresContext* aContext)
@@ -152,25 +134,21 @@ Inside(const gfxRect& aRect, const gfxPo
  */
 static void
 GetAscentAndDescentInAppUnits(nsTextFrame* aFrame,
                               gfxFloat& aAscent, gfxFloat& aDescent)
 {
   gfxSkipCharsIterator it = aFrame->EnsureTextRun(nsTextFrame::eInflated);
   gfxTextRun* textRun = aFrame->GetTextRun(nsTextFrame::eInflated);
 
-  uint32_t offset, length;
-  ConvertOriginalToSkipped(it,
-                           aFrame->GetContentOffset(),
-                           aFrame->GetContentLength(),
-                           offset, length);
+  gfxTextRun::Range range = ConvertOriginalToSkipped(
+    it, aFrame->GetContentOffset(), aFrame->GetContentLength());
 
   gfxTextRun::Metrics metrics =
-    textRun->MeasureText(offset, length, gfxFont::LOOSE_INK_EXTENTS, nullptr,
-                         nullptr);
+    textRun->MeasureText(range, gfxFont::LOOSE_INK_EXTENTS, nullptr, nullptr);
 
   aAscent = metrics.mAscent;
   aDescent = metrics.mDescent;
 }
 
 /**
  * Updates an interval by intersecting it with another interval.
  * The intervals are specified using a start index and a length.
@@ -380,36 +358,36 @@ GetBaselinePosition(nsTextFrame* aFrame,
       return (metrics.mAscent + metrics.mDescent) / 2.0;
   }
 
   NS_NOTREACHED("unexpected dominant-baseline value");
   return aFrame->GetLogicalBaseline(writingMode);
 }
 
 /**
- * For a given text run, returns the number of skipped characters that comprise
+ * For a given text run, returns the range of skipped characters that comprise
  * the ligature group and/or cluster that includes the character represented
  * by the specified gfxSkipCharsIterator.
  *
  * @param aTextRun The text run to use for determining whether a given character
  *   is part of a ligature or cluster.
  * @param aIterator The gfxSkipCharsIterator to use for the current position
  *   in the text run.
  */
-static uint32_t
-ClusterLength(gfxTextRun* aTextRun, const gfxSkipCharsIterator& aIterator)
+static gfxTextRun::Range
+ClusterRange(gfxTextRun* aTextRun, const gfxSkipCharsIterator& aIterator)
 {
   uint32_t start = aIterator.GetSkippedOffset();
   uint32_t end = start + 1;
   while (end < aTextRun->GetLength() &&
          (!aTextRun->IsLigatureGroupStart(end) ||
           !aTextRun->IsClusterStart(end))) {
     end++;
   }
-  return end - start;
+  return gfxTextRun::Range(start, end);
 }
 
 /**
  * Truncates an array to be at most the length of another array.
  *
  * @param aArrayToTruncate The array to truncate.
  * @param aReferenceArray The array whose length will be used to truncate
  *   aArrayToTruncate to.
@@ -476,16 +454,18 @@ namespace mozilla {
  *   - The glyphs are not on a text path
  *   - The glyphs correspond to content within the one nsTextFrame
  *
  * A TextRenderedRunIterator produces TextRenderedRuns required for painting a
  * whole SVGTextFrame.
  */
 struct TextRenderedRun
 {
+  typedef gfxTextRun::Range Range;
+
   /**
    * Constructs a TextRenderedRun that is uninitialized except for mFrame
    * being null.
    */
   TextRenderedRun()
     : mFrame(nullptr)
   {
   }
@@ -933,40 +913,38 @@ TextRenderedRun::GetRunUserSpaceRect(nsP
   nscoord above = vertical ? -self.x : -self.y;
   nscoord below = vertical ? self.XMost() - rect.width
                            : self.YMost() - rect.height;
 
   gfxSkipCharsIterator it = mFrame->EnsureTextRun(nsTextFrame::eInflated);
   gfxTextRun* textRun = mFrame->GetTextRun(nsTextFrame::eInflated);
 
   // Get the content range for this rendered run.
-  uint32_t offset, length;
-  ConvertOriginalToSkipped(it, mTextFrameContentOffset, mTextFrameContentLength,
-                           offset, length);
-  if (length == 0) {
+  Range range = ConvertOriginalToSkipped(it, mTextFrameContentOffset,
+                                         mTextFrameContentLength);
+  if (range.Length() == 0) {
     return r;
   }
 
   // Measure that range.
   gfxTextRun::Metrics metrics =
-    textRun->MeasureText(offset, length, gfxFont::LOOSE_INK_EXTENTS,
-                         nullptr, nullptr);
+    textRun->MeasureText(range, gfxFont::LOOSE_INK_EXTENTS, nullptr, nullptr);
   // Make sure it includes the font-box.
   gfxRect fontBox(0, -metrics.mAscent,
       metrics.mAdvanceWidth, metrics.mAscent + metrics.mDescent);
   metrics.mBoundingBox.UnionRect(metrics.mBoundingBox, fontBox);
 
   // Determine the rectangle that covers the rendered run's fill,
   // taking into account the measured vertical overflow due to
   // decorations.
   nscoord baseline = metrics.mBoundingBox.y + metrics.mAscent;
   gfxFloat x, width;
   if (aFlags & eNoHorizontalOverflow) {
     x = 0.0;
-    width = textRun->GetAdvanceWidth(offset, length, nullptr);
+    width = textRun->GetAdvanceWidth(range, nullptr);
   } else {
     x = metrics.mBoundingBox.x;
     width = metrics.mBoundingBox.width;
   }
   nsRect fillInAppUnits(x, baseline - above,
                         width, metrics.mBoundingBox.height + above + below);
   if (textRun->IsVertical()) {
     // Swap line-relative textMetrics dimensions to physical coordinates.
@@ -1053,67 +1031,63 @@ TextRenderedRun::GetClipEdges(nscoord& a
     return;
   }
 
   gfxSkipCharsIterator it = mFrame->EnsureTextRun(nsTextFrame::eInflated);
   gfxTextRun* textRun = mFrame->GetTextRun(nsTextFrame::eInflated);
 
   // Get the covered content offset/length for this rendered run in skipped
   // characters, since that is what GetAdvanceWidth expects.
-  uint32_t runOffset, runLength, frameOffset, frameLength;
-  ConvertOriginalToSkipped(it, mTextFrameContentOffset, mTextFrameContentLength,
-                               runOffset, runLength);
+  Range runRange = ConvertOriginalToSkipped(it, mTextFrameContentOffset,
+                                            mTextFrameContentLength);
 
   // Get the offset/length of the whole nsTextFrame.
-  frameOffset = mFrame->GetContentOffset();
-  frameLength = mFrame->GetContentLength();
+  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(), true);
   TrimOffsets(frameOffset, frameLength, trimmedOffsets);
 
   // Convert the trimmed whole-nsTextFrame offset/length into skipped
   // characters.
-  ConvertOriginalToSkipped(it, frameOffset, frameLength);
+  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,
-  nscoord startEdge =
-    textRun->GetAdvanceWidth(frameOffset, runOffset - frameOffset, nullptr);
+  nscoord startEdge = textRun->
+    GetAdvanceWidth(Range(frameRange.start, runRange.start), nullptr);
 
   // and between the end of the rendered run's content and the end
   // of the frame's content.
-  nscoord endEdge =
-    textRun->GetAdvanceWidth(runOffset + runLength,
-                             frameOffset + frameLength - (runOffset + runLength),
-                             nullptr);
+  nscoord endEdge = textRun->
+    GetAdvanceWidth(Range(runRange.end, frameRange.end), nullptr);
 
   if (textRun->IsRightToLeft()) {
     aVisIStartEdge = endEdge;
     aVisIEndEdge = startEdge;
   } else {
     aVisIStartEdge = startEdge;
     aVisIEndEdge = endEdge;
   }
 }
 
 nscoord
 TextRenderedRun::GetAdvanceWidth() const
 {
   gfxSkipCharsIterator it = mFrame->EnsureTextRun(nsTextFrame::eInflated);
   gfxTextRun* textRun = mFrame->GetTextRun(nsTextFrame::eInflated);
 
-  uint32_t offset, length;
-  ConvertOriginalToSkipped(it, mTextFrameContentOffset, mTextFrameContentLength,
-                           offset, length);
-
-  return textRun->GetAdvanceWidth(offset, length, nullptr);
+  Range range = ConvertOriginalToSkipped(it, mTextFrameContentOffset,
+                                         mTextFrameContentLength);
+
+  return textRun->GetAdvanceWidth(range, nullptr);
 }
 
 int32_t
 TextRenderedRun::GetCharNumAtPosition(nsPresContext* aContext,
                                       const gfxPoint& aPoint) const
 {
   if (mTextFrameContentLength == 0) {
     return -1;
@@ -1154,37 +1128,34 @@ TextRenderedRun::GetCharNumAtPosition(ns
     }
   }
 
   gfxSkipCharsIterator it = mFrame->EnsureTextRun(nsTextFrame::eInflated);
   gfxTextRun* textRun = mFrame->GetTextRun(nsTextFrame::eInflated);
 
   // Next check that the point lies horizontally within the left and right
   // edges of the text.
-  uint32_t offset, length;
-  ConvertOriginalToSkipped(it, mTextFrameContentOffset, mTextFrameContentLength,
-                           offset, length);
+  Range range = ConvertOriginalToSkipped(it, mTextFrameContentOffset,
+                                         mTextFrameContentLength);
   gfxFloat runAdvance =
-    aContext->AppUnitsToGfxUnits(textRun->GetAdvanceWidth(offset, length,
-                                                          nullptr));
+    aContext->AppUnitsToGfxUnits(textRun->GetAdvanceWidth(range, nullptr));
 
   gfxFloat pos = writingMode.IsVertical() ? p.y : p.x;
   if (pos < 0 || pos >= runAdvance) {
     return -1;
   }
 
   // Finally, measure progressively smaller portions of the rendered run to
   // find which glyph it lies within.  This will need to change once we
   // support letter-spacing and word-spacing.
   bool rtl = textRun->IsRightToLeft();
   for (int32_t i = mTextFrameContentLength - 1; i >= 0; i--) {
-    ConvertOriginalToSkipped(it, mTextFrameContentOffset, i, offset, length);
+    range = ConvertOriginalToSkipped(it, mTextFrameContentOffset, i);
     gfxFloat advance =
-      aContext->AppUnitsToGfxUnits(textRun->GetAdvanceWidth(offset, length,
-                                                            nullptr));
+      aContext->AppUnitsToGfxUnits(textRun->GetAdvanceWidth(range, nullptr));
     if ((rtl && pos < runAdvance - advance) ||
         (!rtl && pos >= advance)) {
       return i;
     }
   }
   return -1;
 }
 
@@ -2132,16 +2103,18 @@ TextRenderedRunIterator::First()
 // -----------------------------------------------------------------------------
 // CharIterator
 
 /**
  * Iterator for characters within an SVGTextFrame.
  */
 class CharIterator
 {
+  typedef gfxTextRun::Range Range;
+
 public:
   /**
    * Values for the aFilter argument of the constructor, to indicate which
    * characters we should be iterating over.
    */
   enum CharacterFilter {
     // Iterate over all original characters from the DOM that are within valid
     // text content elements.
@@ -2623,55 +2596,56 @@ CharIterator::GetOriginalGlyphOffsets(ui
 
 gfxFloat
 CharIterator::GetGlyphAdvance(nsPresContext* aContext) const
 {
   uint32_t offset, length;
   GetOriginalGlyphOffsets(offset, length);
 
   gfxSkipCharsIterator it = TextFrame()->EnsureTextRun(nsTextFrame::eInflated);
-  ConvertOriginalToSkipped(it, offset, length);
+  Range range = ConvertOriginalToSkipped(it, offset, length);
 
   float cssPxPerDevPx = aContext->
     AppUnitsToFloatCSSPixels(aContext->AppUnitsPerDevPixel());
 
-  gfxFloat advance = mTextRun->GetAdvanceWidth(offset, length, nullptr);
+  gfxFloat advance = mTextRun->GetAdvanceWidth(range, nullptr);
   return aContext->AppUnitsToGfxUnits(advance) *
          mLengthAdjustScaleFactor * cssPxPerDevPx;
 }
 
 gfxFloat
 CharIterator::GetAdvance(nsPresContext* aContext) const
 {
   float cssPxPerDevPx = aContext->
     AppUnitsToFloatCSSPixels(aContext->AppUnitsPerDevPixel());
 
-  gfxFloat advance =
-    mTextRun->GetAdvanceWidth(mSkipCharsIterator.GetSkippedOffset(), 1, nullptr);
+  uint32_t offset = mSkipCharsIterator.GetSkippedOffset();
+  gfxFloat advance = mTextRun->
+    GetAdvanceWidth(Range(offset, offset + 1), nullptr);
   return aContext->AppUnitsToGfxUnits(advance) *
          mLengthAdjustScaleFactor * cssPxPerDevPx;
 }
 
 gfxFloat
 CharIterator::GetGlyphPartialAdvance(uint32_t aPartLength,
                                      nsPresContext* aContext) const
 {
   uint32_t offset, length;
   GetOriginalGlyphOffsets(offset, length);
 
   NS_ASSERTION(aPartLength <= length, "invalid aPartLength value");
   length = aPartLength;
 
   gfxSkipCharsIterator it = TextFrame()->EnsureTextRun(nsTextFrame::eInflated);
-  ConvertOriginalToSkipped(it, offset, length);
+  Range range = ConvertOriginalToSkipped(it, offset, length);
 
   float cssPxPerDevPx = aContext->
     AppUnitsToFloatCSSPixels(aContext->AppUnitsPerDevPixel());
 
-  gfxFloat advance = mTextRun->GetAdvanceWidth(offset, length, nullptr);
+  gfxFloat advance = mTextRun->GetAdvanceWidth(range, nullptr);
   return aContext->AppUnitsToGfxUnits(advance) *
          mLengthAdjustScaleFactor * cssPxPerDevPx;
 }
 
 bool
 CharIterator::NextCharacter()
 {
   if (AtEnd()) {
@@ -4213,20 +4187,20 @@ SVGTextFrame::GetSubStringLength(nsICont
 
     if (length != 0) {
       // Convert offset into an index into the frame.
       offset += run.mTextFrameContentOffset - run.mTextElementCharIndex;
 
       gfxSkipCharsIterator it =
         run.mFrame->EnsureTextRun(nsTextFrame::eInflated);
       gfxTextRun* textRun = run.mFrame->GetTextRun(nsTextFrame::eInflated);
-      ConvertOriginalToSkipped(it, offset, length);
+      Range range = ConvertOriginalToSkipped(it, offset, length);
 
       // Accumulate the advance.
-      textLength += textRun->GetAdvanceWidth(offset, length, nullptr);
+      textLength += textRun->GetAdvanceWidth(range, nullptr);
     }
 
     run = it.Next();
   }
 
   nsPresContext* presContext = PresContext();
   float cssPxPerDevPx = presContext->
     AppUnitsToFloatCSSPixels(presContext->AppUnitsPerDevPixel());
@@ -4715,34 +4689,34 @@ SVGTextFrame::DetermineCharPositions(nsT
     }
 
     // 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() &&
            !it.IsOriginalCharSkipped() &&
            (!textRun->IsLigatureGroupStart(it.GetSkippedOffset()) ||
             !textRun->IsClusterStart(it.GetSkippedOffset()))) {
-      nscoord advance = textRun->GetAdvanceWidth(it.GetSkippedOffset(), 1,
-                                                 nullptr);
+      uint32_t offset = it.GetSkippedOffset();
+      nscoord advance = textRun->
+        GetAdvanceWidth(Range(offset, offset + 1), nullptr);
       (textRun->IsVertical() ? position.y : position.x) +=
         textRun->IsRightToLeft() ? -advance : advance;
       aPositions.AppendElement(lastPosition);
       it.AdvanceOriginal(1);
     }
 
     // The meat of the text frame.
     while (it.GetOriginalOffset() < frame->GetContentEnd()) {
       aPositions.AppendElement(position);
       if (!it.IsOriginalCharSkipped() &&
           textRun->IsLigatureGroupStart(it.GetSkippedOffset()) &&
           textRun->IsClusterStart(it.GetSkippedOffset())) {
         // A real visible character.
-        uint32_t length = ClusterLength(textRun, it);
-        nscoord advance = textRun->GetAdvanceWidth(it.GetSkippedOffset(),
-                                                   length, nullptr);
+        nscoord advance = textRun->
+          GetAdvanceWidth(ClusterRange(textRun, it), nullptr);
         (textRun->IsVertical() ? position.y : position.x) +=
           textRun->IsRightToLeft() ? -advance : advance;
         lastPosition = position;
       }
       it.AdvanceOriginal(1);
     }
   }
 
--- a/layout/svg/SVGTextFrame.h
+++ b/layout/svg/SVGTextFrame.h
@@ -7,16 +7,17 @@
 #define MOZILLA_SVGTEXTFRAME_H
 
 #include "mozilla/Attributes.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/gfx/2D.h"
 #include "gfxMatrix.h"
 #include "gfxRect.h"
 #include "gfxSVGGlyphs.h"
+#include "gfxTextRun.h"
 #include "nsIContent.h" // for GetContent
 #include "nsStubMutationObserver.h"
 #include "nsSVGPaintServerFrame.h"
 
 class gfxContext;
 class nsDisplaySVGText;
 class SVGTextFrame;
 class nsTextFrame;
@@ -253,16 +254,17 @@ class SVGTextFrame final : public SVGTex
   friend class mozilla::GlyphMetricsUpdater;
   friend class mozilla::TextFrameIterator;
   friend class mozilla::TextNodeCorrespondenceRecorder;
   friend struct mozilla::TextRenderedRun;
   friend class mozilla::TextRenderedRunIterator;
   friend class MutationObserver;
   friend class nsDisplaySVGText;
 
+  typedef gfxTextRun::Range Range;
   typedef mozilla::gfx::DrawTarget DrawTarget;
   typedef mozilla::gfx::Path Path;
   typedef mozilla::gfx::Point Point;
   typedef mozilla::SVGTextContextPaint SVGTextContextPaint;
 
 protected:
   explicit SVGTextFrame(nsStyleContext* aContext)
     : SVGTextFrameBase(aContext),