commit aa124118714b8171a27d35a2820bd45c51eaec36
parent 4bc276362491d28731154efdf229b95463e4ef29
Author: Jonathan Kew <jkew@mozilla.com>
Date: Sat, 4 Oct 2025 09:10:54 +0000
Bug 1991689 - Cache a PropertyProvider in SVGTextFrame so that we don't have to create a fresh one for every call to the same textframe. r=firefox-svg-reviewers,longsonr
Differential Revision: https://phabricator.services.mozilla.com/D267440
Diffstat:
3 files changed, 29 insertions(+), 17 deletions(-)
diff --git a/layout/generic/nsTextFrame.h b/layout/generic/nsTextFrame.h
@@ -112,9 +112,12 @@ class nsTextFrame : public nsIFrame {
/**
* An implementation of gfxTextRun::PropertyProvider that computes spacing and
* hyphenation based on CSS properties for a text frame.
+ *
+ * nsTextFrame normally creates a PropertyProvider as a temporary object on
+ * on the stack, but this is not marked MOZ_STACK_CLASS because SVGTextFrame
+ * wants to cache an instance across multiple calls using the same textframe.
*/
- class MOZ_STACK_CLASS PropertyProvider final
- : public gfxTextRun::PropertyProvider {
+ class PropertyProvider final : public gfxTextRun::PropertyProvider {
using HyphenType = gfxTextRun::HyphenType;
public:
diff --git a/layout/svg/SVGTextFrame.cpp b/layout/svg/SVGTextFrame.cpp
@@ -835,7 +835,6 @@ SVGBBox TextRenderedRun::GetRunUserSpaceRect(nsPresContext* aContext,
vertical ? -self.y : -self.x);
gfxSkipCharsIterator it = mFrame->EnsureTextRun(nsTextFrame::eInflated);
- gfxSkipCharsIterator start = it;
gfxTextRun* textRun = mFrame->GetTextRun(nsTextFrame::eInflated);
// Get the content range for this rendered run.
@@ -845,10 +844,7 @@ SVGBBox TextRenderedRun::GetRunUserSpaceRect(nsPresContext* aContext,
return r;
}
- // FIXME(heycam): We could create a single PropertyProvider for all
- // TextRenderedRuns that correspond to the text frame, rather than recreate
- // it each time here.
- nsTextFrame::PropertyProvider provider(mFrame, start);
+ auto& provider = mRoot->PropertyProviderFor(mFrame);
// Measure that range.
gfxTextRun::Metrics metrics = textRun->MeasureText(
@@ -946,7 +942,7 @@ void TextRenderedRun::GetClipEdges(nscoord& aVisIStartEdge,
gfxSkipCharsIterator it = mFrame->EnsureTextRun(nsTextFrame::eInflated);
gfxTextRun* textRun = mFrame->GetTextRun(nsTextFrame::eInflated);
- nsTextFrame::PropertyProvider provider(mFrame, it);
+ auto& provider = mRoot->PropertyProviderFor(mFrame);
// Get the covered content offset/length for this rendered run in skipped
// characters, since that is what GetAdvanceWidth expects.
@@ -1018,7 +1014,7 @@ void TextRenderedRun::GetClipEdges(nscoord& aVisIStartEdge,
nscoord TextRenderedRun::GetAdvanceWidth() const {
gfxSkipCharsIterator it = mFrame->EnsureTextRun(nsTextFrame::eInflated);
gfxTextRun* textRun = mFrame->GetTextRun(nsTextFrame::eInflated);
- nsTextFrame::PropertyProvider provider(mFrame, it);
+ auto& provider = mRoot->PropertyProviderFor(mFrame);
Range range = ConvertOriginalToSkipped(it, mTextFrameContentOffset,
mTextFrameContentLength);
@@ -1068,7 +1064,7 @@ int32_t TextRenderedRun::GetCharNumAtPosition(nsPresContext* aContext,
gfxSkipCharsIterator it = mFrame->EnsureTextRun(nsTextFrame::eInflated);
gfxTextRun* textRun = mFrame->GetTextRun(nsTextFrame::eInflated);
- nsTextFrame::PropertyProvider provider(mFrame, it);
+ auto& provider = mRoot->PropertyProviderFor(mFrame);
// Next check that the point lies horizontally within the left and right
// edges of the text.
@@ -2355,10 +2351,7 @@ gfxFloat CharIterator::GetAdvance(nsPresContext* aContext) const {
float cssPxPerDevPx =
nsPresContext::AppUnitsToFloatCSSPixels(aContext->AppUnitsPerDevPixel());
- gfxSkipCharsIterator start =
- TextFrame()->EnsureTextRun(nsTextFrame::eInflated);
- nsTextFrame::PropertyProvider provider(TextFrame(), start);
-
+ auto& provider = mFrameIterator.Root()->PropertyProviderFor(TextFrame());
uint32_t offset = mSkipCharsIterator.GetSkippedOffset();
gfxFloat advance =
mTextRun->GetAdvanceWidth(Range(offset, offset + 1), &provider);
@@ -3714,7 +3707,7 @@ float SVGTextFrame::GetSubStringLengthFastPath(nsIContent* aContent,
gfxSkipCharsIterator it = frame->EnsureTextRun(nsTextFrame::eInflated);
gfxTextRun* textRun = frame->GetTextRun(nsTextFrame::eInflated);
- nsTextFrame::PropertyProvider provider(frame, it);
+ auto& provider = PropertyProviderFor(frame);
Range range = ConvertOriginalToSkipped(it, offset, trimmedLength);
@@ -3782,7 +3775,7 @@ float SVGTextFrame::GetSubStringLengthSlowFallback(nsIContent* aContent,
gfxSkipCharsIterator it =
run.mFrame->EnsureTextRun(nsTextFrame::eInflated);
gfxTextRun* textRun = run.mFrame->GetTextRun(nsTextFrame::eInflated);
- nsTextFrame::PropertyProvider provider(run.mFrame, it);
+ auto& provider = PropertyProviderFor(run.mFrame);
Range range = ConvertOriginalToSkipped(it, offset, length);
@@ -4337,7 +4330,7 @@ void SVGTextFrame::DetermineCharPositions(nsTArray<nsPoint>& aPositions) {
for (nsTextFrame* frame = frit.Current(); frame; frame = frit.Next()) {
gfxSkipCharsIterator it = frame->EnsureTextRun(nsTextFrame::eInflated);
gfxTextRun* textRun = frame->GetTextRun(nsTextFrame::eInflated);
- nsTextFrame::PropertyProvider provider(frame, it);
+ auto& provider = PropertyProviderFor(frame);
// Reset the position to the new frame's position.
position = frit.Position();
@@ -5144,6 +5137,7 @@ void SVGTextFrame::DoReflow() {
// Forget any cached measurements of one of our children.
mFrameForCachedRanges = nullptr;
+ mCachedProvider.reset();
nsPresContext* presContext = PresContext();
nsIFrame* kid = PrincipalChildList().FirstChild();
diff --git a/layout/svg/SVGTextFrame.h b/layout/svg/SVGTextFrame.h
@@ -11,6 +11,7 @@
#include "gfxRect.h"
#include "gfxTextRun.h"
#include "mozilla/Attributes.h"
+#include "mozilla/Maybe.h"
#include "mozilla/PresShellForwards.h"
#include "mozilla/RefPtr.h"
#include "mozilla/SVGContainerFrame.h"
@@ -610,9 +611,23 @@ class SVGTextFrame final : public SVGDisplayContainerFrame {
return mCachedRanges[aWhichRange];
}
+ // Return a reference to a PropertyProvider for the given textframe;
+ // the provider is cached by SVGTextFrame to avoid creating it afresh
+ // for repeated operations involving the same textframe.
+ nsTextFrame::PropertyProvider& PropertyProviderFor(nsTextFrame* aFrame) {
+ if (!mCachedProvider || aFrame != mCachedProvider->GetFrame()) {
+ mCachedProvider.reset();
+ mCachedProvider.emplace(aFrame,
+ aFrame->EnsureTextRun(nsTextFrame::eInflated));
+ }
+ return mCachedProvider.ref();
+ }
+
private:
const nsTextFrame* mFrameForCachedRanges = nullptr;
CachedMeasuredRange mCachedRanges[CachedRangeCount];
+
+ Maybe<nsTextFrame::PropertyProvider> mCachedProvider;
};
} // namespace mozilla