Bug 1509167 - Cache an nsFontMetrics reference in nsTextFrame so that we don't have to re-create it on every DrawText call. r=jwatt
authorJonathan Kew <jkew@mozilla.com>
Mon, 26 Nov 2018 17:59:50 +0000
changeset 507236 8a7a03bc253d12664de2a396b2483187499af996
parent 507235 5fb44413dac138585f4783470e5c9e9d7cf501f3
child 507237 73de720af49df25489f2f47a84c14725e326478c
push id1905
push userffxbld-merge
push dateMon, 21 Jan 2019 12:33:13 +0000
treeherdermozilla-release@c2fca1944d8c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjwatt
bugs1509167
milestone65.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1509167 - Cache an nsFontMetrics reference in nsTextFrame so that we don't have to re-create it on every DrawText call. r=jwatt
layout/generic/nsTextFrame.cpp
layout/generic/nsTextFrame.h
--- a/layout/generic/nsTextFrame.cpp
+++ b/layout/generic/nsTextFrame.cpp
@@ -2035,16 +2035,32 @@ GetFontGroupForFrame(const nsIFrame* aFr
   }
   // XXX this is a bit bogus, we're releasing 'metrics' so the
   // returned font-group might actually be torn down, although because
   // of the way the device context caches font metrics, this seems to
   // not actually happen. But we should fix this.
   return fontGroup;
 }
 
+static gfxFontGroup*
+GetInflatedFontGroupForFrame(nsTextFrame* aFrame)
+{
+  gfxTextRun* textRun = aFrame->GetTextRun(nsTextFrame::eInflated);
+  if (textRun) {
+    return textRun->GetFontGroup();
+  }
+  if (!aFrame->InflatedFontMetrics()) {
+    float inflation = nsLayoutUtils::FontSizeInflationFor(aFrame);
+    RefPtr<nsFontMetrics> metrics =
+      nsLayoutUtils::GetFontMetricsForFrame(aFrame, inflation);
+    aFrame->SetInflatedFontMetrics(metrics);
+  }
+  return aFrame->InflatedFontMetrics()->GetThebesFontGroup();
+}
+
 static already_AddRefed<DrawTarget>
 CreateReferenceDrawTarget(const nsTextFrame* aTextFrame)
 {
   RefPtr<gfxContext> ctx =
     aTextFrame->PresShell()->CreateReferenceRenderingContext();
   RefPtr<DrawTarget> dt = ctx->GetDrawTarget();
   return dt.forget();
 }
@@ -2279,23 +2295,25 @@ BuildTextRunsScanner::BuildTextRunForFra
     finalUserData = userData;
   }
 
   uint32_t transformedLength = currentTransformedTextOffset;
 
   // Now build the textrun
   nsTextFrame* firstFrame = mMappedFlows[0].mStartFrame;
   float fontInflation;
+  gfxFontGroup* fontGroup;
   if (mWhichTextRun == nsTextFrame::eNotInflated) {
     fontInflation = 1.0f;
+    fontGroup = GetFontGroupForFrame(firstFrame, fontInflation);
   } else {
     fontInflation = nsLayoutUtils::FontSizeInflationFor(firstFrame);
-  }
-
-  gfxFontGroup* fontGroup = GetFontGroupForFrame(firstFrame, fontInflation);
+    fontGroup = GetInflatedFontGroupForFrame(firstFrame);
+  }
+
   if (!fontGroup) {
     DestroyUserData(userDataToDestroy);
     return nullptr;
   }
 
   if (flags2 & nsTextFrameUtils::Flags::TEXT_HAS_TAB) {
     flags |= gfx::ShapedTextFlags::TEXT_ENABLE_SPACING;
   }
@@ -3121,18 +3139,20 @@ public:
   }
 
   /**
    * Use this constructor after the frame has been reflowed and we don't
    * have other data around. Gets everything from the frame. EnsureTextRun
    * *must* be called before this!!!
    */
   PropertyProvider(nsTextFrame* aFrame, const gfxSkipCharsIterator& aStart,
-                   nsTextFrame::TextRunType aWhichTextRun)
+                   nsTextFrame::TextRunType aWhichTextRun,
+                   nsFontMetrics* aFontMetrics)
     : mTextRun(aFrame->GetTextRun(aWhichTextRun)), mFontGroup(nullptr),
+      mFontMetrics(aFontMetrics),
       mTextStyle(aFrame->StyleText()),
       mFrag(aFrame->GetContent()->GetText()),
       mLineContainer(nullptr),
       mFrame(aFrame), mStart(aStart), mTempIterator(aStart),
       mTabWidths(nullptr), mTabWidthsAnalyzedLimit(0),
       mLength(aFrame->GetContentLength()),
       mWordSpacing(WordSpacing(aFrame, mTextRun)),
       mLetterSpacing(LetterSpacing(aFrame)),
@@ -3184,17 +3204,17 @@ public:
   uint32_t GetOriginalLength() const {
     NS_ASSERTION(mLength != INT32_MAX, "Length not known");
     return mLength;
   }
   const nsTextFragment* GetFragment() const { return mFrag; }
 
   gfxFontGroup* GetFontGroup() const {
     if (!mFontGroup) {
-      InitFontGroupAndFontMetrics();
+      mFontGroup = GetFontMetrics()->GetThebesFontGroup();
     }
     return mFontGroup;
   }
 
   nsFontMetrics* GetFontMetrics() const {
     if (!mFontMetrics) {
       InitFontGroupAndFontMetrics();
     }
@@ -3211,20 +3231,31 @@ public:
   }
 
   const gfxSkipCharsIterator& GetEndHint() const { return mTempIterator; }
 
 protected:
   void SetupJustificationSpacing(bool aPostReflow);
 
   void InitFontGroupAndFontMetrics() const {
-    float inflation = (mWhichTextRun == nsTextFrame::eInflated)
-      ? mFrame->GetFontSizeInflation() : 1.0f;
-    mFontGroup = GetFontGroupForFrame(mFrame, inflation,
-                                      getter_AddRefs(mFontMetrics));
+    if (!mFontMetrics) {
+      if (mWhichTextRun == nsTextFrame::eInflated) {
+        if (!mFrame->InflatedFontMetrics()) {
+          float inflation = mFrame->GetFontSizeInflation();
+          mFontMetrics =
+            nsLayoutUtils::GetFontMetricsForFrame(mFrame, inflation);
+          mFrame->SetInflatedFontMetrics(mFontMetrics);
+        } else {
+          mFontMetrics = mFrame->InflatedFontMetrics();
+        }
+      } else {
+        mFontMetrics = nsLayoutUtils::GetFontMetricsForFrame(mFrame, 1.0f);
+      }
+    }
+    mFontGroup = mFontMetrics->GetThebesFontGroup();
   }
 
   const RefPtr<gfxTextRun>        mTextRun;
   mutable gfxFontGroup*           mFontGroup;
   mutable RefPtr<nsFontMetrics>   mFontMetrics;
   const nsStyleText*              mTextStyle;
   const nsTextFragment*           mFrag;
   const nsIFrame*                 mLineContainer;
@@ -4755,16 +4786,17 @@ nsTextFrame::SetTextRun(gfxTextRun* aTex
   // (but be aware that text runs can go away).
 }
 
 bool
 nsTextFrame::RemoveTextRun(gfxTextRun* aTextRun)
 {
   if (aTextRun == mTextRun) {
     mTextRun = nullptr;
+    mFontMetrics = nullptr;
     return true;
   }
   if ((GetStateBits() & TEXT_HAS_FONT_INFLATION) &&
       GetProperty(UninflatedTextRunProperty()) == aTextRun) {
     DeleteProperty(UninflatedTextRunProperty());
     return true;
   }
   return false;
@@ -4774,16 +4806,20 @@ void
 nsTextFrame::ClearTextRun(nsTextFrame* aStartContinuation,
                           TextRunType aWhichTextRun)
 {
   RefPtr<gfxTextRun> textRun = GetTextRun(aWhichTextRun);
   if (!textRun) {
     return;
   }
 
+  if (aWhichTextRun == nsTextFrame::eInflated) {
+    mFontMetrics = nullptr;
+  }
+
   DebugOnly<bool> checkmTextrun = textRun == mTextRun;
   UnhookTextRunFromFrames(textRun, aStartContinuation);
   MOZ_ASSERT(checkmTextrun ? !mTextRun
                            : !GetProperty(UninflatedTextRunProperty()));
 }
 
 void
 nsTextFrame::DisconnectTextRuns()
@@ -5533,29 +5569,29 @@ struct EmphasisMarkInfo
   gfxFloat advance;
   gfxFloat baselineOffset;
 };
 
 NS_DECLARE_FRAME_PROPERTY_DELETABLE(EmphasisMarkProperty, EmphasisMarkInfo)
 
 static already_AddRefed<gfxTextRun>
 GenerateTextRunForEmphasisMarks(nsTextFrame* aFrame,
-                                nsFontMetrics* aFontMetrics,
+                                gfxFontGroup* aFontGroup,
                                 ComputedStyle* aComputedStyle,
                                 const nsStyleText* aStyleText)
 {
   const nsString& emphasisString = aStyleText->mTextEmphasisStyleString;
   RefPtr<DrawTarget> dt = CreateReferenceDrawTarget(aFrame);
   auto appUnitsPerDevUnit = aFrame->PresContext()->AppUnitsPerDevPixel();
   gfx::ShapedTextFlags flags = nsLayoutUtils::GetTextRunOrientFlagsForStyle(aComputedStyle);
   if (flags == gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED) {
     // The emphasis marks should always be rendered upright per spec.
     flags = gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT;
   }
-  return aFontMetrics->GetThebesFontGroup()->
+  return aFontGroup->
     MakeTextRun<char16_t>(emphasisString.get(), emphasisString.Length(),
                           dt, appUnitsPerDevUnit, flags,
                           nsTextFrameUtils::Flags(), nullptr);
 }
 
 static nsRubyFrame*
 FindFurthestInlineRubyAncestor(nsTextFrame* aFrame)
 {
@@ -5585,17 +5621,18 @@ nsTextFrame::UpdateTextEmphasis(WritingM
     computedStyle = GetParent()->Style();
   }
   RefPtr<nsFontMetrics> fm =
     nsLayoutUtils::GetFontMetricsOfEmphasisMarks(computedStyle,
                                                  PresContext(),
                                                  GetFontSizeInflation());
   EmphasisMarkInfo* info = new EmphasisMarkInfo;
   info->textRun =
-    GenerateTextRunForEmphasisMarks(this, fm, computedStyle, styleText);
+    GenerateTextRunForEmphasisMarks(this, fm->GetThebesFontGroup(),
+                                    computedStyle, styleText);
   info->advance = info->textRun->GetAdvanceWidth();
 
   // Calculate the baseline offset
   LogicalSide side = styleText->TextEmphasisSide(aWM);
   LogicalSize frameSize = GetLogicalSize(aWM);
   // The overflow rect is inflated in the inline direction by half
   // advance of the emphasis mark on each side, so that even if a mark
   // is drawn for a zero-width character, it won't be clipped.
@@ -6720,17 +6757,17 @@ nsTextFrame::DrawEmphasisMarks(gfxContex
 
 nscolor
 nsTextFrame::GetCaretColorAt(int32_t aOffset)
 {
   MOZ_ASSERT(aOffset >= 0, "aOffset must be positive");
 
   nscolor result = nsFrame::GetCaretColorAt(aOffset);
   gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
-  PropertyProvider provider(this, iter, nsTextFrame::eInflated);
+  PropertyProvider provider(this, iter, nsTextFrame::eInflated, mFontMetrics);
   int32_t contentOffset = provider.GetStart().GetOriginalOffset();
   int32_t contentLength = provider.GetOriginalLength();
   MOZ_ASSERT(aOffset >= contentOffset &&
              aOffset <= contentOffset + contentLength,
              "aOffset must be in the frame's range");
 
   int32_t offsetInFrame = aOffset - contentOffset;
   if (offsetInFrame < 0 || offsetInFrame >= contentLength) {
@@ -6791,17 +6828,17 @@ nsTextFrame::MeasureCharClippedText(nsco
 {
   // We need a *reference* rendering context (not one that might have a
   // transform), so we don't have a rendering context argument.
   // XXX get the block and line passed to us somehow! This is slow!
   gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
   if (!mTextRun)
     return false;
 
-  PropertyProvider provider(this, iter, nsTextFrame::eInflated);
+  PropertyProvider provider(this, iter, nsTextFrame::eInflated, mFontMetrics);
   // Trim trailing whitespace
   provider.InitializeForDisplay(true);
 
   Range range = ComputeTransformedRange(provider);
   uint32_t startOffset = range.start;
   uint32_t maxLength = range.Length();
   return MeasureCharClippedText(provider, aVisIStartEdge, aVisIEndEdge,
                                 &startOffset, &maxLength,
@@ -6977,17 +7014,17 @@ nsTextFrame::PaintText(const PaintTextPa
   // Don't pass in the rendering context here, because we need a
   // *reference* context and rendering context might have some transform
   // in it
   // XXX get the block and line passed to us somehow! This is slow!
   gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
   if (!mTextRun)
     return;
 
-  PropertyProvider provider(this, iter, nsTextFrame::eInflated);
+  PropertyProvider provider(this, iter, nsTextFrame::eInflated, mFontMetrics);
   if (aItem.mIsFrameSelected.isNothing()) {
     aItem.mIsFrameSelected.emplace(IsSelected());
   }
   // Trim trailing whitespace, unless we're painting a selection highlight,
   // which should include trailing spaces if present (bug 1146754).
   provider.InitializeForDisplay(!aItem.mIsFrameSelected.value());
 
   const bool reversed = mTextRun->IsInlineReversed();
@@ -7496,17 +7533,17 @@ nsTextFrame::GetCharacterOffsetAtFramePo
                                                     bool aForInsertionPoint)
 {
   ContentOffsets offsets;
 
   gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
   if (!mTextRun)
     return offsets;
 
-  PropertyProvider provider(this, iter, nsTextFrame::eInflated);
+  PropertyProvider provider(this, iter, nsTextFrame::eInflated, mFontMetrics);
   // 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);
   if (Style()->IsTextCombined()) {
     width /= GetTextCombineScaleFactor(this);
   }
@@ -7588,19 +7625,17 @@ bool
 nsTextFrame::CombineSelectionUnderlineRect(nsPresContext* aPresContext,
                                            nsRect& aRect)
 {
   if (aRect.IsEmpty())
     return false;
 
   nsRect givenRect = aRect;
 
-  RefPtr<nsFontMetrics> fm =
-    nsLayoutUtils::GetFontMetricsForFrame(this, GetFontSizeInflation());
-  gfxFontGroup* fontGroup = fm->GetThebesFontGroup();
+  gfxFontGroup* fontGroup = GetInflatedFontGroupForFrame(this);
   gfxFont* firstFont = fontGroup->GetFirstValidFont();
   WritingMode wm = GetWritingMode();
   bool verticalRun = wm.IsVertical();
   bool useVerticalMetrics = verticalRun && !wm.IsSideways();
   const gfxFont::Metrics& metrics =
     firstFont->GetMetrics(useVerticalMetrics ? gfxFont::eVertical
                                              : gfxFont::eHorizontal);
 
@@ -7790,17 +7825,17 @@ nsTextFrame::GetPointFromOffset(int32_t 
     outPoint->y = 0;
     return NS_OK;
   }
 
   gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
   if (!mTextRun)
     return NS_ERROR_FAILURE;
 
-  PropertyProvider properties(this, iter, nsTextFrame::eInflated);
+  PropertyProvider properties(this, iter, nsTextFrame::eInflated, mFontMetrics);
   // Don't trim trailing whitespace, we want the caret to appear in the right
   // place if it's positioned there
   properties.InitializeForDisplay(false);
 
   UpdateIteratorFromOffset(properties, inOffset, iter);
 
   *outPoint = GetPointFromIterator(iter, properties);
 
@@ -7821,17 +7856,17 @@ nsTextFrame::GetCharacterRectsInRange(in
     return NS_OK;
   }
 
   if (!mTextRun) {
     return NS_ERROR_FAILURE;
   }
 
   gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
-  PropertyProvider properties(this, iter, nsTextFrame::eInflated);
+  PropertyProvider properties(this, iter, nsTextFrame::eInflated, mFontMetrics);
   // Don't trim trailing whitespace, we want the caret to appear in the right
   // place if it's positioned there
   properties.InitializeForDisplay(false);
 
   UpdateIteratorFromOffset(properties, aInOffset, iter);
 
   const int32_t kContentEnd = GetContentEnd();
   const int32_t kEndOffset = std::min(aInOffset + aLength, kContentEnd);
@@ -8719,16 +8754,17 @@ nsTextFrame::AddInlineMinISize(gfxContex
 {
   float inflation = nsLayoutUtils::FontSizeInflationFor(this);
   TextRunType trtype = (inflation == 1.0f) ? eNotInflated : eInflated;
 
   if (trtype == eInflated && !IsCurrentFontInflation(inflation)) {
     // FIXME: Ideally, if we already have a text run, we'd move it to be
     // the uninflated text run.
     ClearTextRun(nullptr, nsTextFrame::eInflated);
+    mFontMetrics = nullptr;
   }
 
   nsTextFrame* f;
   const gfxTextRun* lastTextRun = nullptr;
   // nsContinuingTextFrame does nothing for AddInlineMinISize; all text frames
   // in the flow are handled right here.
   for (f = this; f; f = f->GetNextContinuation()) {
     // f->GetTextRun(nsTextFrame::eNotInflated) could be null if we
@@ -8871,16 +8907,17 @@ nsTextFrame::AddInlinePrefISize(gfxConte
 {
   float inflation = nsLayoutUtils::FontSizeInflationFor(this);
   TextRunType trtype = (inflation == 1.0f) ? eNotInflated : eInflated;
 
   if (trtype == eInflated && !IsCurrentFontInflation(inflation)) {
     // FIXME: Ideally, if we already have a text run, we'd move it to be
     // the uninflated text run.
     ClearTextRun(nullptr, nsTextFrame::eInflated);
+    mFontMetrics = nullptr;
   }
 
   nsTextFrame* f;
   const gfxTextRun* lastTextRun = nullptr;
   // nsContinuingTextFrame does nothing for AddInlineMinISize; all text frames
   // in the flow are handled right here.
   for (f = this; f; f = f->GetNextContinuation()) {
     // f->GetTextRun(nsTextFrame::eNotInflated) could be null if we
@@ -8939,17 +8976,17 @@ nsTextFrame::ComputeTightBounds(DrawTarg
   }
 
   gfxSkipCharsIterator iter =
     const_cast<nsTextFrame*>(this)->EnsureTextRun(nsTextFrame::eInflated);
   if (!mTextRun)
     return nsRect(0, 0, 0, 0);
 
   PropertyProvider provider(const_cast<nsTextFrame*>(this), iter,
-                            nsTextFrame::eInflated);
+                            nsTextFrame::eInflated, mFontMetrics);
   // Trim trailing whitespace
   provider.InitializeForDisplay(true);
 
   gfxTextRun::Metrics metrics =
         mTextRun->MeasureText(ComputeTransformedRange(provider),
                               gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS,
                               aDrawTarget, &provider);
   if (GetWritingMode().IsLineInverted()) {
@@ -8973,17 +9010,17 @@ nsTextFrame::GetPrefWidthTightBounds(gfx
                                      nscoord* aXMost)
 {
   gfxSkipCharsIterator iter =
     const_cast<nsTextFrame*>(this)->EnsureTextRun(nsTextFrame::eInflated);
   if (!mTextRun)
     return NS_ERROR_FAILURE;
 
   PropertyProvider provider(const_cast<nsTextFrame*>(this), iter,
-                            nsTextFrame::eInflated);
+                            nsTextFrame::eInflated, mFontMetrics);
   provider.InitializeForMeasure();
 
   gfxTextRun::Metrics metrics =
         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);
@@ -9451,16 +9488,17 @@ nsTextFrame::ReflowText(nsLineLayout& aL
   }
 
   float fontSizeInflation = nsLayoutUtils::FontSizeInflationFor(this);
 
   if (!IsCurrentFontInflation(fontSizeInflation)) {
     // FIXME: Ideally, if we already have a text run, we'd move it to be
     // the uninflated text run.
     ClearTextRun(nullptr, nsTextFrame::eInflated);
+    mFontMetrics = nullptr;
   }
 
   gfxSkipCharsIterator iter =
     EnsureTextRun(nsTextFrame::eInflated, aDrawTarget,
                   lineContainer, aLineLayout.GetLine(), &flowEndInTextRun);
 
   NS_ASSERTION(IsCurrentFontInflation(fontSizeInflation),
                "EnsureTextRun should have set font size inflation");
@@ -9966,17 +10004,17 @@ nsTextFrame::RecomputeOverflow(nsIFrame*
 {
   nsRect bounds(nsPoint(0, 0), GetSize());
   nsOverflowAreas result(bounds, bounds);
 
   gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
   if (!mTextRun)
     return result;
 
-  PropertyProvider provider(this, iter, nsTextFrame::eInflated);
+  PropertyProvider provider(this, iter, nsTextFrame::eInflated, mFontMetrics);
   // Don't trim trailing space, in case we need to paint it as selected.
   provider.InitializeForDisplay(false);
 
   gfxTextRun::Metrics textMetrics =
     mTextRun->MeasureText(ComputeTransformedRange(provider),
                           gfxFont::LOOSE_INK_EXTENTS, nullptr,
                           &provider);
   if (GetWritingMode().IsLineInverted()) {
--- a/layout/generic/nsTextFrame.h
+++ b/layout/generic/nsTextFrame.h
@@ -14,16 +14,17 @@
 #include "mozilla/dom/CharacterData.h"
 #include "nsFrame.h"
 #include "nsFrameSelection.h"
 #include "nsSplittableFrame.h"
 #include "nsLineBox.h"
 #include "gfxSkipChars.h"
 #include "gfxTextRun.h"
 #include "nsDisplayList.h"
+#include "nsFontMetrics.h"
 #include "JustificationUtils.h"
 #include "RubyUtils.h"
 
 // Undo the windows.h damage
 #if defined(XP_WIN) && defined(DrawText)
 #undef DrawText
 #endif
 
@@ -665,19 +666,27 @@ public:
 
   /**
    * Call this after you have manually changed the text node contents without
    * notifying that change.  This behaves as if all the text contents changed.
    * (You should only use this for native anonymous content.)
    */
   void NotifyNativeAnonymousTextnodeChange(uint32_t aOldLength);
 
+  void SetInflatedFontMetrics(nsFontMetrics* aMetrics) {
+    mFontMetrics = aMetrics;
+  }
+  nsFontMetrics* InflatedFontMetrics() const {
+    return mFontMetrics;
+  }
+
 protected:
   virtual ~nsTextFrame();
 
+  RefPtr<nsFontMetrics> mFontMetrics;
   RefPtr<gfxTextRun> mTextRun;
   nsTextFrame* mNextContinuation;
   // The key invariant here is that mContentOffset never decreases along
   // a next-continuation chain. And of course mContentOffset is always <= the
   // the text node's content length, and the mContentOffset for the first frame
   // is always 0. Furthermore the text mapped by a frame is determined by
   // GetContentOffset() and GetContentLength()/GetContentEnd(), which get
   // the length from the difference between this frame's offset and the next