Bug 655877 - Part 31b: New frame class nsSVGTextFrame2 for SVG text using CSS block/inline/text frames for layout. r=jwatt
authorCameron McCormack <cam@mcc.id.au>
Mon, 11 Feb 2013 17:22:16 +1100
changeset 121490 76dd3bdc30dfbb249c8d59fe694a2f64d76560cb
parent 121489 9642172270f18e067e2a6762942ba5d7f41687ee
child 121491 53005a0154076fbad15da799ebaafb6f1f5bc5ca
push id24291
push userryanvm@gmail.com
push dateMon, 11 Feb 2013 19:12:51 +0000
treeherdermozilla-central@93ba23f414ff [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjwatt
bugs655877
milestone21.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 655877 - Part 31b: New frame class nsSVGTextFrame2 for SVG text using CSS block/inline/text frames for layout. r=jwatt
content/svg/content/src/SVGTextPathElement.h
layout/base/nsDisplayItemTypesList.h
layout/generic/nsFrameIdList.h
layout/svg/Makefile.in
layout/svg/nsSVGContainerFrame.cpp
layout/svg/nsSVGOuterSVGFrame.cpp
layout/svg/nsSVGTextFrame2.cpp
layout/svg/nsSVGTextFrame2.h
--- a/content/svg/content/src/SVGTextPathElement.h
+++ b/content/svg/content/src/SVGTextPathElement.h
@@ -39,16 +39,17 @@ static const unsigned short TEXTPATH_SPA
 
 typedef SVGTextContentElement SVGTextPathElementBase;
 
 class SVGTextPathElement MOZ_FINAL : public SVGTextPathElementBase,
                                      public nsIDOMSVGElement,
                                      public nsIDOMSVGURIReference
 {
 friend class ::nsSVGTextPathFrame;
+friend class ::nsSVGTextFrame2;
 
 protected:
   friend nsresult (::NS_NewSVGTextPathElement(nsIContent **aResult,
                                               already_AddRefed<nsINodeInfo> aNodeInfo));
   SVGTextPathElement(already_AddRefed<nsINodeInfo> aNodeInfo);
   virtual JSObject* WrapNode(JSContext *cx, JSObject *scope, bool *triedToWrap) MOZ_OVERRIDE;
 
 public:
--- a/layout/base/nsDisplayItemTypesList.h
+++ b/layout/base/nsDisplayItemTypesList.h
@@ -43,16 +43,17 @@ DECLARE_DISPLAY_ITEM_TYPE(REMOTE_SHADOW)
 DECLARE_DISPLAY_ITEM_TYPE(SCROLL_LAYER)
 DECLARE_DISPLAY_ITEM_TYPE(SCROLL_INFO_LAYER)
 DECLARE_DISPLAY_ITEM_TYPE(SELECTION_OVERLAY)
 DECLARE_DISPLAY_ITEM_TYPE(SOLID_COLOR)
 DECLARE_DISPLAY_ITEM_TYPE(SVG_EFFECTS)
 DECLARE_DISPLAY_ITEM_TYPE(SVG_GLYPHS)
 DECLARE_DISPLAY_ITEM_TYPE(SVG_OUTER_SVG)
 DECLARE_DISPLAY_ITEM_TYPE(SVG_PATH_GEOMETRY)
+DECLARE_DISPLAY_ITEM_TYPE(SVG_TEXT)
 DECLARE_DISPLAY_ITEM_TYPE(TABLE_CELL_BACKGROUND)
 DECLARE_DISPLAY_ITEM_TYPE(TABLE_CELL_SELECTION)
 DECLARE_DISPLAY_ITEM_TYPE(TABLE_ROW_BACKGROUND)
 DECLARE_DISPLAY_ITEM_TYPE(TABLE_ROW_GROUP_BACKGROUND)
 DECLARE_DISPLAY_ITEM_TYPE(TABLE_BORDER_BACKGROUND)
 DECLARE_DISPLAY_ITEM_TYPE(TEXT)
 DECLARE_DISPLAY_ITEM_TYPE(TEXT_DECORATION)
 DECLARE_DISPLAY_ITEM_TYPE(TEXT_OVERFLOW)
--- a/layout/generic/nsFrameIdList.h
+++ b/layout/generic/nsFrameIdList.h
@@ -149,16 +149,17 @@ FRAME_ID(nsSVGOuterSVGAnonChildFrame)
 FRAME_ID(nsSVGPaintServerFrame)
 FRAME_ID(nsSVGPathGeometryFrame)
 FRAME_ID(nsSVGPatternFrame)
 FRAME_ID(nsSVGRadialGradientFrame)
 FRAME_ID(nsSVGStopFrame)
 FRAME_ID(nsSVGSwitchFrame)
 FRAME_ID(nsSVGTextContainerFrame)
 FRAME_ID(nsSVGTextFrame)
+FRAME_ID(nsSVGTextFrame2)
 FRAME_ID(nsSVGTextPathFrame)
 FRAME_ID(nsSVGTSpanFrame)
 FRAME_ID(nsSVGUseFrame)
 FRAME_ID(SVGViewFrame)
 FRAME_ID(nsTableCaptionFrame)
 FRAME_ID(nsTableCellFrame)
 FRAME_ID(nsTableColFrame)
 FRAME_ID(nsTableColGroupFrame)
--- a/layout/svg/Makefile.in
+++ b/layout/svg/Makefile.in
@@ -43,16 +43,17 @@ CPPSRCS		= \
 		nsSVGOuterSVGFrame.cpp \
 		nsSVGPaintServerFrame.cpp \
 		nsSVGPathGeometryFrame.cpp \
 		nsSVGPatternFrame.cpp \
 		nsSVGStopFrame.cpp \
 		nsSVGSwitchFrame.cpp \
 		nsSVGTextContainerFrame.cpp \
 		nsSVGTextFrame.cpp \
+		nsSVGTextFrame2.cpp \
 		nsSVGTextPathFrame.cpp \
 		nsSVGTSpanFrame.cpp \
 		nsSVGUseFrame.cpp \
 		SVGViewFrame.cpp \
 		nsSVGUtils.cpp \
 		$(NULL)
 
 include $(topsrcdir)/config/config.mk
@@ -72,16 +73,17 @@ include $(topsrcdir)/config/rules.mk
 
 LOCAL_INCLUDES	= \
 		-I$(srcdir)/../base \
 		-I$(srcdir)/../generic \
 		-I$(srcdir)/../style \
 		-I$(srcdir)/../xul/base/src \
 		-I$(srcdir)/../../content/svg/content/src \
 		-I$(srcdir)/../../content/base/src \
+		-I$(srcdir)/../../widget \
 		$(NULL)
 
 libs::
 	$(INSTALL) $(srcdir)/svg.css $(DIST)/bin/res
 
 install::
 	$(SYSINSTALL) $(IFLAGS1) $(srcdir)/svg.css $(DESTDIR)$(mozappdir)/res
 
--- a/layout/svg/nsSVGContainerFrame.cpp
+++ b/layout/svg/nsSVGContainerFrame.cpp
@@ -266,16 +266,17 @@ nsSVGDisplayContainerFrame::ReflowSVG()
   nsOverflowAreas overflowRects;
 
   for (nsIFrame* kid = mFrames.FirstChild(); kid;
        kid = kid->GetNextSibling()) {
     nsISVGChildFrame* SVGFrame = do_QueryFrame(kid);
     if (SVGFrame) {
       NS_ABORT_IF_FALSE(!(kid->GetStateBits() & NS_STATE_SVG_NONDISPLAY_CHILD),
                         "Check for this explicitly in the |if|, then");
+      kid->AddStateBits(mState & NS_FRAME_IS_DIRTY);
       SVGFrame->ReflowSVG();
 
       // We build up our child frame overflows here instead of using
       // nsLayoutUtils::UnionChildOverflow since SVG frame's all use the same
       // frame list, and we're iterating over that list now anyway.
       ConsiderChildOverflow(overflowRects, kid);
     }
   }
--- a/layout/svg/nsSVGOuterSVGFrame.cpp
+++ b/layout/svg/nsSVGOuterSVGFrame.cpp
@@ -455,16 +455,17 @@ nsSVGOuterSVGFrame::Reflow(nsPresContext
   if (!(GetStateBits() & NS_STATE_SVG_NONDISPLAY_CHILD)) {
     // Now that we've marked the necessary children as dirty, call
     // ReflowSVG() on them:
 
     mCallingReflowSVG = true;
 
     // Update the mRects and visual overflow rects of all our descendants,
     // including our anonymous wrapper kid:
+    anonKid->AddStateBits(mState & NS_FRAME_IS_DIRTY);
     anonKid->ReflowSVG();
     NS_ABORT_IF_FALSE(!anonKid->GetNextSibling(),
       "We should have one anonymous child frame wrapping our real children");
 
     mCallingReflowSVG = false;
   }
 
   // Make sure we scroll if we're too big:
new file mode 100644
--- /dev/null
+++ b/layout/svg/nsSVGTextFrame2.cpp
@@ -0,0 +1,4551 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Main header first:
+#include "nsSVGTextFrame.h"
+
+// Keep others in (case-insensitive) order:
+#include "DOMSVGPoint.h"
+#include "gfxFont.h"
+#include "gfxSkipChars.h"
+#include "gfxTypes.h"
+#include "LookAndFeel.h"
+#include "nsBlockFrame.h"
+#include "nsCaret.h"
+#include "nsContentUtils.h"
+#include "nsGkAtoms.h"
+#include "nsIDOMSVGAnimatedNumber.h"
+#include "nsIDOMSVGLength.h"
+#include "nsIDOMSVGRect.h"
+#include "nsISVGGlyphFragmentNode.h"
+#include "nsISelection.h"
+#include "nsQuickSort.h"
+#include "nsRenderingContext.h"
+#include "nsSVGEffects.h"
+#include "nsSVGGlyphFrame.h"
+#include "nsSVGOuterSVGFrame.h"
+#include "nsSVGRect.h"
+#include "nsSVGIntegrationUtils.h"
+#include "nsSVGTextFrame2.h"
+#include "nsSVGTextPathFrame.h"
+#include "nsSVGUtils.h"
+#include "nsTArray.h"
+#include "nsTextFrame.h"
+#include "nsTextNode.h"
+#include "SVGAnimatedNumberList.h"
+#include "SVGContentUtils.h"
+#include "SVGLengthList.h"
+#include "SVGNumberList.h"
+#include "SVGPathElement.h"
+#include "SVGTextPathElement.h"
+#include <algorithm>
+#include <cmath>
+#include <limits>
+
+using namespace mozilla;
+
+// ============================================================================
+// 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).
+ */
+static void
+ConvertOriginalToSkipped(gfxSkipCharsIterator& aIterator,
+                         uint32_t aOriginalOffset, uint32_t aOriginalLength,
+                         uint32_t& aSkippedOffset, uint32_t& aSkippedLength)
+{
+  aSkippedOffset = 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);
+}
+
+/**
+ * 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)
+{
+  return gfxPoint(aContext->AppUnitsToGfxUnits(aPoint.x),
+                  aContext->AppUnitsToGfxUnits(aPoint.y));
+}
+
+/**
+ * Converts a gfxRect that is in app units to CSS pixels using the specified
+ * nsPresContext and returns it as a gfxRect.
+ */
+static gfxRect
+AppUnitsToFloatCSSPixels(const gfxRect& aRect, const nsPresContext* aContext)
+{
+  return gfxRect(aContext->AppUnitsToFloatCSSPixels(aRect.x),
+                 aContext->AppUnitsToFloatCSSPixels(aRect.y),
+                 aContext->AppUnitsToFloatCSSPixels(aRect.width),
+                 aContext->AppUnitsToFloatCSSPixels(aRect.height));
+}
+
+/**
+ * Scales a gfxRect around a given point.
+ *
+ * @param aRect The rectangle to scale.
+ * @param aPoint The point around which to scale.
+ * @param aScale The scale amount.
+ */
+static void
+ScaleAround(gfxRect& aRect, const gfxPoint& aPoint, double aScale)
+{
+  aRect.x = aPoint.x - aScale * (aPoint.x - aRect.x);
+  aRect.y = aPoint.y - aScale * (aPoint.y - aRect.y);
+  aRect.width *= aScale;
+  aRect.height *= aScale;
+}
+
+/**
+ * Gets the measured ascent and descent of the text in the given nsTextFrame
+ * in app units.
+ *
+ * @param aFrame The text frame.
+ * @param aAscent The ascent in app units (output).
+ * @param aDescent The descent in app units (output).
+ */
+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::Metrics metrics =
+    textRun->MeasureText(offset, length, 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.
+ */
+static void
+IntersectInterval(uint32_t& aStart, uint32_t& aLength,
+                  uint32_t aStartOther, uint32_t aLengthOther)
+{
+  uint32_t aEnd = aStart + aLength;
+  uint32_t aEndOther = aStartOther + aLengthOther;
+
+  if (aStartOther >= aEnd || aStart >= aEndOther) {
+    aLength = 0;
+  } else {
+    if (aStartOther >= aStart)
+      aStart = aStartOther;
+    aLength = std::min(aEnd, aEndOther) - aStart;
+  }
+}
+
+/**
+ * Intersects an interval as IntersectInterval does but by taking
+ * the offset and length of the other interval from a
+ * nsTextFrame::TrimmedOffsets object.
+ */
+static void
+TrimOffsets(uint32_t& aStart, uint32_t& aLength,
+            const nsTextFrame::TrimmedOffsets& aTrimmedOffsets)
+{
+  IntersectInterval(aStart, aLength,
+                    aTrimmedOffsets.mStart, aTrimmedOffsets.mLength);
+}
+
+/**
+ * Returns the closest ancestor-or-self node that is not an SVG <a>
+ * element.
+ */
+static nsIContent*
+GetFirstNonAAncestor(nsIContent* aContent)
+{
+  while (aContent && aContent->IsSVG(nsGkAtoms::a)) {
+    aContent = aContent->GetParent();
+  }
+  return aContent;
+}
+
+/**
+ * Returns whether the given node is a text content element[1], taking into
+ * account whether it has a valid parent.
+ *
+ * For example, in:
+ *
+ *   <svg xmlns="http://www.w3.org/2000/svg">
+ *     <text><a/><text/></text>
+ *     <tspan/>
+ *   </svg>
+ *
+ * true would be returned for the outer <text> element and the <a> element,
+ * and false for the inner <text> element (since a <text> is not allowed
+ * to be a child of another <text>) and the <tspan> element (because it
+ * must be inside a <text> subtree).
+ *
+ * Note that we don't support the <tref> element yet and this function
+ * returns false for it.
+ *
+ * [1] https://svgwg.org/svg2-draft/intro.html#TermTextContentElement
+ */
+static bool
+IsTextContentElement(nsIContent* aContent)
+{
+  if (!aContent->IsSVG()) {
+    return false;
+  }
+
+  if (aContent->Tag() == nsGkAtoms::text) {
+    nsIContent* parent = GetFirstNonAAncestor(aContent->GetParent());
+    return !parent || !IsTextContentElement(parent);
+  }
+
+  if (aContent->Tag() == nsGkAtoms::textPath) {
+    nsIContent* parent = GetFirstNonAAncestor(aContent->GetParent());
+    return parent && parent->IsSVG(nsGkAtoms::text);
+  }
+
+  if (aContent->Tag() == nsGkAtoms::a ||
+      aContent->Tag() == nsGkAtoms::tspan ||
+      aContent->Tag() == nsGkAtoms::tref ||
+      aContent->Tag() == nsGkAtoms::altGlyph) {
+    return true;
+  }
+
+  return false;
+}
+
+/**
+ * Returns whether the specified frame is an nsTextFrame that has some text
+ * content.
+ */
+static bool
+IsNonEmptyTextFrame(nsIFrame* aFrame)
+{
+  nsTextFrame* textFrame = do_QueryFrame(aFrame);
+  if (!textFrame) {
+    return false;
+  }
+
+  nsIContent* content = textFrame->GetContent();
+  NS_ASSERTION(content && content->IsNodeOfType(nsINode::eTEXT),
+               "unexpected content type for nsTextFrame");
+
+  return static_cast<nsTextNode*>(content)->TextLength() != 0;
+}
+
+/**
+ * Takes an nsIFrame and if it is a text frame that has some text content,
+ * returns it as an nsTextFrame and its corresponding nsTextNode.
+ *
+ * @param aFrame The frame to look at.
+ * @param aTextFrame aFrame as an nsTextFrame (output).
+ * @param aTextNode The nsTextNode content of aFrame (output).
+ * @return true if aFrame is a non-empty text frame, false otherwise.
+ */
+static bool
+GetNonEmptyTextFrameAndNode(nsIFrame* aFrame,
+                            nsTextFrame*& aTextFrame,
+                            nsTextNode*& aTextNode)
+{
+  nsTextFrame* text = do_QueryFrame(aFrame);
+  if (!text) {
+    return false;
+  }
+
+  nsIContent* content = text->GetContent();
+  NS_ASSERTION(content && content->IsNodeOfType(nsINode::eTEXT),
+               "unexpected content type for nsTextFrame");
+
+  nsTextNode* node = static_cast<nsTextNode*>(content);
+  if (node->TextLength() == 0) {
+    return false;
+  }
+
+  aTextFrame = text;
+  aTextNode = node;
+  return true;
+}
+
+/**
+ * Returns whether the specified atom is for one of the five
+ * glyph positioning attributes that can appear on SVG text
+ * elements -- x, y, dx, dy or rotate.
+ */
+static bool
+IsGlyphPositioningAttribute(nsIAtom* aAttribute)
+{
+  return aAttribute == nsGkAtoms::x ||
+         aAttribute == nsGkAtoms::y ||
+         aAttribute == nsGkAtoms::dx ||
+         aAttribute == nsGkAtoms::dy ||
+         aAttribute == nsGkAtoms::rotate;
+}
+
+/**
+ * Returns the position in app units of a given baseline (using an
+ * SVG dominant-baseline property value) for a given nsTextFrame.
+ *
+ * @param aFrame The text frame to inspect.
+ * @param aTextRun The text run of aFrame.
+ * @param aDominantBaseline The dominant-baseline value to use.
+ */
+static nscoord
+GetBaselinePosition(nsTextFrame* aFrame,
+                    gfxTextRun* aTextRun,
+                    uint8_t aDominantBaseline)
+{
+  switch (aDominantBaseline) {
+    case NS_STYLE_DOMINANT_BASELINE_HANGING:
+    case NS_STYLE_DOMINANT_BASELINE_TEXT_BEFORE_EDGE:
+      return 0;
+    case NS_STYLE_DOMINANT_BASELINE_AUTO:
+    case NS_STYLE_DOMINANT_BASELINE_ALPHABETIC:
+      return aFrame->GetBaseline();
+  }
+
+  gfxTextRun::Metrics metrics =
+    aTextRun->MeasureText(0, aTextRun->GetLength(), gfxFont::LOOSE_INK_EXTENTS,
+                          nullptr, nullptr);
+
+  switch (aDominantBaseline) {
+    case NS_STYLE_DOMINANT_BASELINE_TEXT_AFTER_EDGE:
+    case NS_STYLE_DOMINANT_BASELINE_IDEOGRAPHIC:
+      return metrics.mAscent + metrics.mDescent;
+    case NS_STYLE_DOMINANT_BASELINE_CENTRAL:
+    case NS_STYLE_DOMINANT_BASELINE_MIDDLE:
+      return (metrics.mAscent + metrics.mDescent) / 2.0;
+  }
+
+  NS_NOTREACHED("unexpected dominant-baseline value");
+  return aFrame->GetBaseline();
+}
+
+/**
+ * For a given text run, returns the number 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)
+{
+  uint32_t start = aIterator.GetSkippedOffset();
+  uint32_t end = start + 1;
+  while (end < aTextRun->GetLength() &&
+         (!aTextRun->IsLigatureGroupStart(end) ||
+          !aTextRun->IsClusterStart(end))) {
+    end++;
+  }
+  return end - start;
+}
+
+/**
+ * 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.
+ */
+template<typename T, typename U>
+static void
+TruncateTo(nsTArray<T>& aArrayToTruncate, const nsTArray<U>& aReferenceArray)
+{
+  uint32_t length = aReferenceArray.Length();
+  if (aArrayToTruncate.Length() > length) {
+    aArrayToTruncate.TruncateLength(length);
+  }
+}
+
+
+// ============================================================================
+// Utility classes
+
+namespace mozilla {
+
+// ----------------------------------------------------------------------------
+// TextRenderedRun
+
+/**
+ * A run of text within a single nsTextFrame whose glyphs can all be painted
+ * with a single call to nsTextFrame::PaintText.  A text rendered run can
+ * be created for a sequence of two or more consecutive glyphs as long as:
+ *
+ *   - Only the first glyph has (or none of the glyphs have) been positioned
+ *     with SVG text positioning attributes
+ *   - All of the glyphs have zero rotation
+ *   - 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 nsSVGTextFrame2.
+ */
+struct TextRenderedRun
+{
+  /**
+   * Constructs a TextRenderedRun that is uninitialized except for mFrame
+   * being null.
+   */
+  TextRenderedRun()
+    : mFrame(nullptr)
+  {
+  }
+
+  /**
+   * Constructs a TextRenderedRun with all of the information required to
+   * paint it.  See the comments documenting the member variables below
+   * for descriptions of the arguments.
+   */
+  TextRenderedRun(nsTextFrame* aFrame, const gfxPoint& aPosition,
+                  double aRotate, float aFontSizeScaleFactor, nscoord aBaseline,
+                  uint32_t aTextFrameContentOffset,
+                  uint32_t aTextFrameContentLength,
+                  uint32_t aTextElementCharIndex)
+    : mFrame(aFrame),
+      mPosition(aPosition),
+      mRotate(static_cast<float>(aRotate)),
+      mFontSizeScaleFactor(aFontSizeScaleFactor),
+      mBaseline(aBaseline),
+      mTextFrameContentOffset(aTextFrameContentOffset),
+      mTextFrameContentLength(aTextFrameContentLength),
+      mTextElementCharIndex(aTextElementCharIndex)
+  {
+  }
+
+  /**
+   * Returns the text run for the text frame that this rendered run is part of.
+   */
+  gfxTextRun* GetTextRun() const
+  {
+    mFrame->EnsureTextRun(nsTextFrame::eInflated);
+    return mFrame->GetTextRun(nsTextFrame::eInflated);
+  }
+
+  /**
+   * Returns whether this rendered run is RTL.
+   */
+  bool IsRightToLeft() const
+  {
+    return GetTextRun()->IsRightToLeft();
+  }
+
+  /**
+   * Returns the transform that converts from a <text> element's user space into
+   * the coordinate space that rendered runs can be painted directly in.
+   *
+   * The difference between this method and GetTransformFromRunUserSpaceToUserSpace
+   * is that when calling in to nsTextFrame::PaintText, it will already take
+   * into account any left clip edge (that is, it doesn't just apply a visual
+   * clip to the rendered text, it shifts the glyphs over so that they are
+   * painted with their left edge at the x coordinate passed in to it).
+   * Thus we need to account for this in our transform.
+   *
+   *
+   * Assume that we have <text x="100" y="100" rotate="0 0 1 0 0 1">abcdef</text>.
+   * This would result in four text rendered runs:
+   *
+   *   - one for "ab"
+   *   - one for "c"
+   *   - one for "de"
+   *   - one for "f"
+   *
+   * Assume now that we are painting the third TextRenderedRun.  It will have
+   * a left clip edge that is the sum of the advances of "abc", and it will
+   * have a right clip edge that is the advance of "f".  In
+   * nsSVGTextFrame2::PaintSVG(), we pass in nsPoint() (i.e., the origin)
+   * as the point at which to paint the text frame, and we pass in the
+   * clip edge values.  The nsTextFrame will paint the substring of its
+   * text such that the top-left corner of the "d"'s glyph cell will be at
+   * (0, 0) in the current coordinate system.
+   *
+   * Thus, GetTransformFromUserSpaceForPainting must return a transform from
+   * whatever user space the <text> element is in to a coordinate space in
+   * device pixels (as that's what nsTextFrame works in) where the origin is at
+   * the same position as our user space mPositions[i].mPosition value for
+   * the "d" glyph, which will be (100 + userSpaceAdvance("abc"), 100).
+   * The translation required to do this (ignoring the scale to get from
+   * user space to device pixels, and ignoring the
+   * (100 + userSpaceAdvance("abc"), 100) translation) is:
+   *
+   *   (-leftEdge, -baseline)
+   *
+   * where baseline is the distance between the baseline of the text and the top
+   * edge of the nsTextFrame.  We translate by -leftEdge horizontally because
+   * the nsTextFrame will already shift the glyphs over by that amount and start
+   * painting glyphs at x = 0.  We translate by -baseline vertically so that
+   * painting the top edges of the glyphs at y = 0 will result in their
+   * baselines being at our desired y position.
+   *
+   *
+   * Now for an example with RTL text.  Assume our content is now
+   * <text x="100" y="100" rotate="0 0 1 0 0 1">WERBEH</text>.  We'd have
+   * the following text rendered runs:
+   *
+   *   - one for "EH"
+   *   - one for "B"
+   *   - one for "ER"
+   *   - one for "W"
+   *
+   * Again, we are painting the third TextRenderedRun.  The left clip edge
+   * is the advance of the "W" and the right clip edge is the sum of the
+   * advances of "BEH".  Our translation to get the rendered "ER" glyphs
+   * in the right place this time is:
+   *
+   *   (-frameWidth + rightEdge, -baseline)
+   *
+   * which is equivalent to:
+   *
+   *   (-(leftEdge + advance("ER")), -baseline)
+   *
+   * The reason we have to shift left additionally by the width of the run
+   * of glyphs we are painting is that although the nsTextFrame is RTL,
+   * we still supply the top-left corner to paint the frame at when calling
+   * nsTextFrame::PaintText, even though our user space positions for each
+   * glyph in mPositions specifies the origin of each glyph, which for RTL
+   * glyphs is at the right edge of the glyph cell.
+   *
+   *
+   * For any other use of an nsTextFrame in the context of a particular run
+   * (such as hit testing, or getting its rectangle),
+   * GetTransformFromRunUserSpaceToUserSpace should be used.
+   *
+   * @param aContext The context to use for unit conversions.
+   * @param aItem The nsCharClipDisplayItem that holds the amount of clipping
+   *   from the left and right edges of the text frame for this rendered run.
+   *   An appropriate nsCharClipDisplayItem can be obtained by constructing an
+   *   SVGCharClipDisplayItem for the TextRenderedRun.
+   */
+  gfxMatrix GetTransformFromUserSpaceForPainting(
+                                      nsPresContext* aContext,
+                                      const nsCharClipDisplayItem& aItem) const;
+
+  /**
+   * Returns the transform that converts from "run user space" to a <text>
+   * element's user space.  Run user space is a coordinate system that has the
+   * same size as the <text>'s user space but rotated and translated such that
+   * (0,0) is the top-left of the rectangle that bounds the text.
+   *
+   * @param aContext The context to use for unit conversions.
+   */
+  gfxMatrix GetTransformFromRunUserSpaceToUserSpace(nsPresContext* aContext) const;
+
+  /**
+   * Returns the transform that converts from "run user space" to float pixels
+   * relative to the nsTextFrame that this rendered run is a part of.
+   *
+   * @param aContext The context to use for unit conversions.
+   */
+  gfxMatrix GetTransformFromRunUserSpaceToFrameUserSpace(nsPresContext* aContext) const;
+
+  /**
+   * Flag values used for the aFlags arguments of GetRunUserSpaceRect,
+   * GetFrameUserSpaceRect and GetUserSpaceRect.
+   */
+  enum {
+    // Includes the fill geometry of the text in the returned rectangle.
+    eIncludeFill = 1,
+    // Includes the stroke geometry of the text in the returned rectangle.
+    eIncludeStroke = 2,
+    // Don't include any horizontal glyph overflow in the returned rectangle.
+    eNoHorizontalOverflow = 4
+  };
+
+  /**
+   * Returns a rectangle that bounds the fill and/or stroke of the rendered run
+   * in run user space.
+   *
+   * @param aContext The context to use for unit conversions.
+   * @param aFlags A combination of the flags above (eIncludeFill and
+   *   eIncludeStroke) indicating what parts of the text to include in
+   *   the rectangle.
+   */
+  gfxRect GetRunUserSpaceRect(nsPresContext* aContext, uint32_t aFlags) const;
+
+  /**
+   * Returns a rectangle that covers the fill and/or stroke of the rendered run
+   * in "frame user space".
+   *
+   * Frame user space is a coordinate space of the same scale as the <text>
+   * element's user space, but with its rotation set to the rotation of
+   * the glyphs within this rendered run and its origin set to the position
+   * such that placing the nsTextFrame there would result in the glyphs in
+   * this rendered run being at their correct positions.
+   *
+   * For example, say we have <text x="100 150" y="100">ab</text>.  Assume
+   * the advance of both the "a" and the "b" is 12 user units, and the
+   * ascent of the text is 8 user units and its descent is 6 user units,
+   * and that we are not measuing the stroke of the text, so that we stay
+   * entirely within the glyph cells.
+   *
+   * There will be two text rendered runs, one for "a" and one for "b".
+   *
+   * The frame user space for the "a" run will have its origin at
+   * (100, 100 - 8) in the <text> element's user space and will have its
+   * axes aligned with the user space (since there is no rotate="" or
+   * text path involve) and with its scale the same as the user space.
+   * The rect returned by this method will be (0, 0, 12, 14), since the "a"
+   * glyph is right at the left of the nsTextFrame.
+   *
+   * The frame user space for the "b" run will have its origin at
+   * (150 - 12, 100 - 8), and scale/rotation the same as above.  The rect
+   * returned by this method will be (12, 0, 12, 14), since we are
+   * advance("a") horizontally in to the text frame.
+   *
+   * @param aContext The context to use for unit conversions.
+   * @param aFlags A combination of the flags above (eIncludeFill and
+   *   eIncludeStroke) indicating what parts of the text to include in
+   *   the rectangle.
+   */
+  gfxRect GetFrameUserSpaceRect(nsPresContext* aContext, uint32_t aFlags) const;
+
+  /**
+   * Returns a rectangle that covers the fill and/or stroke of the rendered run
+   * in the <text> element's user space.
+   *
+   * @param aContext The context to use for unit conversions.
+   * @param aFlags A combination of the flags above indicating what parts of the
+   *   text to include in the rectangle.
+   * @param aAdditionalTransform An additional transform to apply to the
+   *   frame user space rectangle before its bounds are transformed into
+   *   user space.
+   */
+  gfxRect GetUserSpaceRect(nsPresContext* aContext, uint32_t aFlags,
+                           const gfxMatrix* aAdditionalTransform = nullptr) const;
+
+  /**
+   * Gets the app unit amounts to clip from the left and right edges of
+   * the nsTextFrame in order to paint just this rendered run.
+   *
+   * Note that if clip edge amounts land in the middle of a glyph, the
+   * glyph won't be painted at all.  The clip edges are thus more of
+   * a selection mechanism for which glyphs will be painted, rather
+   * than a geometric clip.
+   */
+  void GetClipEdges(nscoord& aLeftEdge, nscoord& aRightEdge) const;
+
+  /**
+   * Returns the advance width of the whole rendered run.
+   */
+  nscoord GetAdvanceWidth() const;
+
+  /**
+   * Returns the index of the character into this rendered run whose
+   * glyph cell contains the given point, or -1 if there is no such
+   * character.  This does not hit test against any overflow.
+   *
+   * @param aContext The context to use for unit conversions.
+   * @param aPoint The point in the user space of the <text> element.
+   */
+  int32_t GetCharNumAtPosition(nsPresContext* aContext,
+                               const gfxPoint& aPoint) const;
+
+  /**
+   * The text frame that this rendered run lies within.
+   */
+  nsTextFrame* mFrame;
+
+  /**
+   * The point in user space that the text is positioned at.
+   *
+   * The x coordinate is the left edge of a LTR run of text or the right edge of
+   * an RTL run.  The y coordinate is the baseline of the text.
+   */
+  gfxPoint mPosition;
+
+  /**
+   * The rotation in radians in the user coordinate system that the text has.
+   */
+  float mRotate;
+
+  /**
+   * The scale factor that was used to transform the text run's original font
+   * size into a sane range for painting and measurement.
+   */
+  double mFontSizeScaleFactor;
+
+  /**
+   * The baseline in app units of this text run.  The measurement is from the
+   * top of the text frame.
+   */
+  nscoord mBaseline;
+
+  /**
+   * The offset and length in mFrame's content nsTextNode that corresponds to
+   * this text rendered run.  These are original char indexes.
+   */
+  uint32_t mTextFrameContentOffset;
+  uint32_t mTextFrameContentLength;
+
+  /**
+   * The character index in the whole SVG <text> element that this text rendered
+   * run begins at.
+   */
+  uint32_t mTextElementCharIndex;
+};
+
+gfxMatrix
+TextRenderedRun::GetTransformFromUserSpaceForPainting(
+                                       nsPresContext* aContext,
+                                       const nsCharClipDisplayItem& aItem) const
+{
+  // We transform to device pixels positioned such that painting the text frame
+  // at (0,0) with aItem will result in the text being in the right place.
+
+  gfxMatrix m;
+  if (!mFrame) {
+    return m;
+  }
+
+  float cssPxPerDevPx = aContext->
+    AppUnitsToFloatCSSPixels(aContext->AppUnitsPerDevPixel());
+
+  // Glyph position in user space.
+  m.Translate(mPosition / cssPxPerDevPx);
+
+  // Take into account any font size scaling.
+  m.Scale(1.0 / mFontSizeScaleFactor, 1.0 / mFontSizeScaleFactor);
+
+  // Rotation due to rotate="" or a <textPath>.
+  m.Rotate(mRotate);
+
+  // Translation to get the text frame in the right place.
+  nsPoint t(IsRightToLeft() ?
+              -mFrame->GetRect().width + aItem.mRightEdge :
+              -aItem.mLeftEdge,
+            -mBaseline);
+  m.Translate(AppUnitsToGfxUnits(t, aContext));
+
+  return m;
+}
+
+gfxMatrix
+TextRenderedRun::GetTransformFromRunUserSpaceToUserSpace(
+                                                  nsPresContext* aContext) const
+{
+  gfxMatrix m;
+  if (!mFrame) {
+    return m;
+  }
+
+  float cssPxPerDevPx = aContext->
+    AppUnitsToFloatCSSPixels(aContext->AppUnitsPerDevPixel());
+
+  nscoord left, right;
+  GetClipEdges(left, right);
+
+  // Glyph position in user space.
+  m.Translate(mPosition);
+
+  // Rotation due to rotate="" or a <textPath>.
+  m.Rotate(mRotate);
+
+  // Translation to get the text frame in the right place.
+  nsPoint t(IsRightToLeft() ?
+              -mFrame->GetRect().width + left + right :
+              0,
+            -mBaseline);
+  m.Translate(AppUnitsToGfxUnits(t, aContext) *
+                cssPxPerDevPx / mFontSizeScaleFactor);
+
+  return m;
+}
+
+gfxMatrix
+TextRenderedRun::GetTransformFromRunUserSpaceToFrameUserSpace(
+                                                  nsPresContext* aContext) const
+{
+  gfxMatrix m;
+  if (!mFrame) {
+    return m;
+  }
+
+  nscoord left, right;
+  GetClipEdges(left, right);
+
+  // Translate by the horizontal distance into the text frame this
+  // rendered run is.
+  return m.Translate(gfxPoint(gfxFloat(left) / aContext->AppUnitsPerCSSPixel(),
+                              0));
+}
+
+gfxRect
+TextRenderedRun::GetRunUserSpaceRect(nsPresContext* aContext,
+                                     uint32_t aFlags) const
+{
+  gfxRect r;
+  if (!mFrame) {
+    return r;
+  }
+
+  // Determine the amount of overflow above and below the frame's mRect.
+  //
+  // We need to call GetVisualOverflowRectRelativeToSelf because this includes
+  // overflowing decorations, which the MeasureText call below does not.  We
+  // assume here the decorations only overflow above and below the frame, never
+  // horizontally.
+  nsRect self = mFrame->GetVisualOverflowRectRelativeToSelf();
+  nsRect rect = mFrame->GetRect();
+  nscoord above = -self.y;
+  nscoord below = 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);
+
+  // Measure that range.
+  gfxTextRun::Metrics metrics =
+    textRun->MeasureText(offset, length, gfxFont::LOOSE_INK_EXTENTS,
+                         nullptr, nullptr);
+
+  // 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);
+  } else {
+    x = metrics.mBoundingBox.x;
+    width = metrics.mBoundingBox.width;
+  }
+  gfxRect fillInAppUnits(x, baseline - above,
+                         width, metrics.mBoundingBox.height + above + below);
+
+  // Convert the app units rectangle to user units.
+  gfxRect fill = AppUnitsToFloatCSSPixels(fillInAppUnits, aContext);
+
+  // Scale the rectangle up due to any mFontSizeScaleFactor.  We scale
+  // it around the text's origin.
+  ScaleAround(fill,
+              gfxPoint(0.0, aContext->AppUnitsToFloatCSSPixels(baseline)),
+              1.0 / mFontSizeScaleFactor);
+
+  // Include the fill if requested.
+  if (aFlags & eIncludeFill) {
+    r = fill;
+  }
+
+  // Include the stroke if requested.
+  if ((aFlags & eIncludeStroke) &&
+      nsSVGUtils::GetStrokeWidth(mFrame) > 0) {
+    r = r.Union(nsSVGUtils::PathExtentsToMaxStrokeExtents(fill, mFrame,
+                                                          gfxMatrix()));
+  }
+
+  return r;
+}
+
+gfxRect
+TextRenderedRun::GetFrameUserSpaceRect(nsPresContext* aContext,
+                                       uint32_t aFlags) const
+{
+  gfxRect r = GetRunUserSpaceRect(aContext, aFlags);
+  gfxMatrix m = GetTransformFromRunUserSpaceToFrameUserSpace(aContext);
+  return m.TransformBounds(r);
+}
+
+gfxRect
+TextRenderedRun::GetUserSpaceRect(nsPresContext* aContext,
+                                  uint32_t aFlags,
+                                  const gfxMatrix* aAdditionalTransform) const
+{
+  gfxRect r = GetRunUserSpaceRect(aContext, aFlags);
+  gfxMatrix m = GetTransformFromRunUserSpaceToUserSpace(aContext);
+  if (aAdditionalTransform) {
+    m.Multiply(*aAdditionalTransform);
+  }
+  return m.TransformBounds(r);
+}
+
+void
+TextRenderedRun::GetClipEdges(nscoord& aLeftEdge, nscoord& aRightEdge) const
+{
+  uint32_t contentLength = mFrame->GetContentLength();
+  if (mTextFrameContentOffset == 0 &&
+      mTextFrameContentLength == contentLength) {
+    // If the rendered run covers the entire content, we know we don't need
+    // to clip without having to measure anything.
+    aLeftEdge = 0;
+    aRightEdge = 0;
+    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);
+
+  // Get the offset/length of the whole nsTextFrame.
+  frameOffset = mFrame->GetContentOffset();
+  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);
+
+  // 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 leftEdge =
+    textRun->GetAdvanceWidth(frameOffset, runOffset - frameOffset, nullptr);
+
+  // and between the end of the rendered run's content and the end
+  // of the frame's content.
+  nscoord rightEdge =
+    textRun->GetAdvanceWidth(runOffset + runLength,
+                             frameOffset + frameLength - (runOffset + runLength),
+                             nullptr);
+
+  if (textRun->IsRightToLeft()) {
+    aLeftEdge  = rightEdge;
+    aRightEdge = leftEdge;
+  } else {
+    aLeftEdge  = leftEdge;
+    aRightEdge = rightEdge;
+  }
+}
+
+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);
+}
+
+int32_t
+TextRenderedRun::GetCharNumAtPosition(nsPresContext* aContext,
+                                      const gfxPoint& aPoint) const
+{
+  if (mTextFrameContentLength == 0) {
+    return -1;
+  }
+
+  // Convert the point from user space into run user space, and take
+  // into account any mFontSizeScaleFactor.
+  gfxMatrix m = GetTransformFromRunUserSpaceToUserSpace(aContext).Invert();
+  gfxPoint p = m.Transform(aPoint) * mFontSizeScaleFactor;
+
+  // First check that the point lies vertically between the top and bottom
+  // edges of the text.
+  gfxFloat ascent, descent;
+  GetAscentAndDescentInAppUnits(mFrame, ascent, descent);
+
+  gfxFloat topEdge = mFrame->GetBaseline() - ascent;
+  gfxFloat bottomEdge = topEdge + ascent + descent;
+
+  if (p.y < aContext->AppUnitsToGfxUnits(topEdge) ||
+      p.y >= aContext->AppUnitsToGfxUnits(bottomEdge)) {
+    return -1;
+  }
+
+  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);
+  gfxFloat runAdvance =
+    aContext->AppUnitsToGfxUnits(textRun->GetAdvanceWidth(offset, length,
+                                                          nullptr));
+
+  if (p.x < 0 || p.x >= 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);
+    gfxFloat advance =
+      aContext->AppUnitsToGfxUnits(textRun->GetAdvanceWidth(offset, length,
+                                                            nullptr));
+    if ((rtl && p.x < runAdvance - advance) ||
+        (!rtl && p.x >= advance)) {
+      return i;
+    }
+  }
+  return -1;
+}
+
+// ----------------------------------------------------------------------------
+// TextNodeIterator
+
+enum SubtreePosition
+{
+  eBeforeSubtree,
+  eWithinSubtree,
+  eAfterSubtree
+};
+
+/**
+ * An iterator class for nsTextNodes that are descendants of a given node, the
+ * root.  Nodes are iterated in document order.  An optional subtree can be
+ * specified, in which case the iterator will track whether the current state of
+ * the traversal over the tree is within that subtree or is past that subtree.
+ */
+class TextNodeIterator
+{
+public:
+  /**
+   * Constructs a TextNodeIterator with the specified root node and optional
+   * subtree.
+   */
+  TextNodeIterator(nsIContent* aRoot, nsIContent* aSubtree = nullptr)
+    : mRoot(aRoot),
+      mSubtree(aSubtree == aRoot ? nullptr : aSubtree),
+      mCurrent(aRoot),
+      mSubtreePosition(mSubtree ? eBeforeSubtree : eWithinSubtree)
+  {
+    NS_ASSERTION(aRoot, "expected non-null root");
+    if (!aRoot->IsNodeOfType(nsINode::eTEXT)) {
+      Next();
+    }
+  }
+
+  /**
+   * Returns the current nsTextNode, or null if the iterator has finished.
+   */
+  nsTextNode* Current() const
+  {
+    return static_cast<nsTextNode*>(mCurrent);
+  }
+
+  /**
+   * Advances to the next nsTextNode and returns it, or null if the end of
+   * iteration has been reached.
+   */
+  nsTextNode* Next();
+
+  /**
+   * Returns whether the iterator is currently within the subtree rooted
+   * at mSubtree.  Returns true if we are not tracking a subtree (we consider
+   * that we're always within the subtree).
+   */
+  bool IsWithinSubtree() const
+  {
+    return mSubtreePosition == eWithinSubtree;
+  }
+
+  /**
+   * Returns whether the iterator is past the subtree rooted at mSubtree.
+   * Returns false if we are not tracking a subtree.
+   */
+  bool IsAfterSubtree() const
+  {
+    return mSubtreePosition == eAfterSubtree;
+  }
+
+private:
+  /**
+   * The root under which all nsTextNodes will be iterated over.
+   */
+  nsIContent* mRoot;
+
+  /**
+   * The node rooting the subtree to track.
+   */
+  nsIContent* mSubtree;
+
+  /**
+   * The current node during iteration.
+   */
+  nsIContent* mCurrent;
+
+  /**
+   * The current iterator position relative to mSubtree.
+   */
+  SubtreePosition mSubtreePosition;
+};
+
+nsTextNode*
+TextNodeIterator::Next()
+{
+  // Starting from mCurrent, we do a non-recursive traversal to the next
+  // nsTextNode beneath mRoot, updating mSubtreePosition appropriately if we
+  // encounter mSubtree.
+  if (mCurrent) {
+    do {
+      nsIContent* next = IsTextContentElement(mCurrent) ?
+                           mCurrent->GetFirstChild() :
+                           nullptr;
+      if (next) {
+        mCurrent = next;
+        if (mCurrent == mSubtree) {
+          mSubtreePosition = eWithinSubtree;
+        }
+      } else {
+        for (;;) {
+          if (mCurrent == mRoot) {
+            mCurrent = nullptr;
+            break;
+          }
+          if (mCurrent == mSubtree) {
+            mSubtreePosition = eAfterSubtree;
+          }
+          next = mCurrent->GetNextSibling();
+          if (next) {
+            mCurrent = next;
+            if (mCurrent == mSubtree) {
+              mSubtreePosition = eWithinSubtree;
+            }
+            break;
+          }
+          if (mCurrent == mSubtree) {
+            mSubtreePosition = eAfterSubtree;
+          }
+          mCurrent = mCurrent->GetParent();
+        }
+      }
+    } while (mCurrent && !mCurrent->IsNodeOfType(nsINode::eTEXT));
+  }
+
+  return static_cast<nsTextNode*>(mCurrent);
+}
+
+// ----------------------------------------------------------------------------
+// TextNodeCorrespondenceRecorder
+
+/**
+ * TextNodeCorrespondence is used as the value of a frame property that
+ * is stored on all its descendant nsTextFrames.  It stores the number of DOM
+ * characters between it and the previous nsTextFrame that did not have an
+ * nsTextFrame created for them, due to either not being in a correctly
+ * parented text content element, or because they were display:none.
+ * These are called "undisplayed characters".
+ *
+ * See also TextNodeCorrespondenceRecorder below, which is what sets the
+ * frame property.
+ */
+struct TextNodeCorrespondence
+{
+  TextNodeCorrespondence(uint32_t aUndisplayedCharacters)
+    : mUndisplayedCharacters(aUndisplayedCharacters)
+  {
+  }
+
+  uint32_t mUndisplayedCharacters;
+};
+
+static void DestroyTextNodeCorrespondence(void* aPropertyValue)
+{
+  delete static_cast<TextNodeCorrespondence*>(aPropertyValue);
+}
+
+NS_DECLARE_FRAME_PROPERTY(TextNodeCorrespondenceProperty, DestroyTextNodeCorrespondence)
+
+/**
+ * Returns the number of undisplayed characters before the specified
+ * nsTextFrame.
+ */
+static uint32_t
+GetUndisplayedCharactersBeforeFrame(nsTextFrame* aFrame)
+{
+  void* value = aFrame->Properties().Get(TextNodeCorrespondenceProperty());
+  TextNodeCorrespondence* correspondence =
+    static_cast<TextNodeCorrespondence*>(value);
+  if (!correspondence) {
+    NS_NOTREACHED("expected a TextNodeCorrespondenceProperty on nsTextFrame "
+                  "used for SVG text");
+    return 0;
+  }
+  return correspondence->mUndisplayedCharacters;
+}
+
+/**
+ * Traverses the nsTextFrames for an nsSVGTextFrame2 and records a
+ * TextNodeCorrespondenceProperty on each for the number of undisplayed DOM
+ * characters between each frame.  This is done by iterating simultaenously
+ * over the nsTextNodes and nsTextFrames and noting when nsTextNodes (or
+ * parts of them) are skipped when finding the next nsTextFrame.
+ */
+class TextNodeCorrespondenceRecorder
+{
+public:
+  /**
+   * Entry point for the TextNodeCorrespondenceProperty recording.
+   */
+  static void RecordCorrespondence(nsSVGTextFrame2* aRoot);
+
+private:
+  TextNodeCorrespondenceRecorder(nsSVGTextFrame2* aRoot)
+    : mNodeIterator(aRoot->GetContent()),
+      mPreviousNode(nullptr),
+      mNodeCharIndex(0)
+  {
+  }
+
+  void Record(nsSVGTextFrame2* aRoot);
+  void TraverseAndRecord(nsIFrame* aFrame);
+
+  /**
+   * Returns the next non-empty nsTextNode.
+   */
+  nsTextNode* NextNode();
+
+  /**
+   * The iterator over the nsTextNodes that we use as we simultaneously
+   * iterate over the nsTextFrames.
+   */
+  TextNodeIterator mNodeIterator;
+
+  /**
+   * The previous nsTextNode we iterated over.
+   */
+  nsTextNode* mPreviousNode;
+
+  /**
+   * The index into the current nsTextNode's character content.
+   */
+  uint32_t mNodeCharIndex;
+};
+
+/* static */ void
+TextNodeCorrespondenceRecorder::RecordCorrespondence(nsSVGTextFrame2* aRoot)
+{
+  TextNodeCorrespondenceRecorder recorder(aRoot);
+  recorder.Record(aRoot);
+}
+
+void
+TextNodeCorrespondenceRecorder::Record(nsSVGTextFrame2* aRoot)
+{
+  if (!mNodeIterator.Current()) {
+    // If there are no nsTextNodes then there is nothing to do.
+    return;
+  }
+
+  // Traverse over all the nsTextFrames and record the number of undisplayed
+  // characters.
+  TraverseAndRecord(aRoot);
+
+  // Find how many undisplayed characters there are after the final nsTextFrame.
+  uint32_t undisplayed = 0;
+  if (mNodeIterator.Current()) {
+    if (mPreviousNode && mPreviousNode->TextLength() != mNodeCharIndex) {
+      // The last nsTextFrame ended part way through an nsTextNode.  The
+      // remaining characters count as undisplayed.
+      NS_ASSERTION(mNodeCharIndex < mPreviousNode->TextLength(),
+                   "incorrect tracking of undisplayed characters in "
+                   "text nodes");
+      undisplayed += mPreviousNode->TextLength() - mNodeCharIndex;
+    }
+    // All the remaining nsTextNodes that we iterate must also be undisplayed.
+    for (nsTextNode* textNode = mNodeIterator.Current();
+         textNode;
+         textNode = NextNode()) {
+      undisplayed += textNode->TextLength();
+    }
+  }
+
+  // Record the trailing number of undisplayed characters on the
+  // nsSVGTextFrame2.
+  aRoot->mTrailingUndisplayedCharacters = undisplayed;
+}
+
+nsTextNode*
+TextNodeCorrespondenceRecorder::NextNode()
+{
+  mPreviousNode = mNodeIterator.Current();
+  nsTextNode* next;
+  do {
+    next = mNodeIterator.Next();
+  } while (next && next->TextLength() == 0);
+  return next;
+}
+
+void
+TextNodeCorrespondenceRecorder::TraverseAndRecord(nsIFrame* aFrame)
+{
+  // Recursively iterate over the frame tree, for frames that correspond
+  // to text content elements.
+  if (IsTextContentElement(aFrame->GetContent())) {
+    for (nsIFrame* f = aFrame->GetFirstPrincipalChild();
+         f;
+         f = f->GetNextSibling()) {
+      TraverseAndRecord(f);
+    }
+    return;
+  }
+
+  nsTextFrame* frame;  // The current text frame.
+  nsTextNode* node;    // The text node for the current text frame.
+  if (!GetNonEmptyTextFrameAndNode(aFrame, frame, node)) {
+    // If this isn't an nsTextFrame, or is empty, nothing to do.
+    return;
+  }
+
+  NS_ASSERTION(frame->GetContentOffset() >= 0,
+               "don't know how to handle negative content indexes");
+
+  uint32_t undisplayed = 0;
+  if (!mPreviousNode) {
+    // Must be the very first text frame.
+    NS_ASSERTION(mNodeCharIndex == 0, "incorrect tracking of undisplayed "
+                                      "characters in text nodes");
+    if (!mNodeIterator.Current()) {
+      NS_NOTREACHED("incorrect tracking of correspondence between text frames "
+                    "and text nodes");
+    } else {
+      // Each whole nsTextNode we find before we get to the text node for the
+      // first text frame must be undisplayed.
+      while (mNodeIterator.Current() != node) {
+        undisplayed += mNodeIterator.Current()->TextLength();
+        NextNode();
+      }
+      // If the first text frame starts at a non-zero content offset, then those
+      // earlier characters are also undisplayed.
+      undisplayed += frame->GetContentOffset();
+      NextNode();
+    }
+  } else if (mPreviousNode == node) {
+    // Same text node as last time.
+    if (static_cast<uint32_t>(frame->GetContentOffset()) != mNodeCharIndex) {
+      // We have some characters in the middle of the text node
+      // that are undisplayed.
+      NS_ASSERTION(mNodeCharIndex <
+                     static_cast<uint32_t>(frame->GetContentOffset()),
+                   "incorrect tracking of undisplayed characters in "
+                   "text nodes");
+      undisplayed = frame->GetContentOffset() - mNodeCharIndex;
+    }
+  } else {
+    // Different text node from last time.
+    if (mPreviousNode->TextLength() != mNodeCharIndex) {
+      NS_ASSERTION(mNodeCharIndex < mPreviousNode->TextLength(),
+                   "incorrect tracking of undisplayed characters in "
+                   "text nodes");
+      // Any trailing characters at the end of the previous nsTextNode are
+      // undisplayed.
+      undisplayed = mPreviousNode->TextLength() - mNodeCharIndex;
+    }
+    // Each whole nsTextNode we find before we get to the text node for
+    // the current text frame must be undisplayed.
+    while (mNodeIterator.Current() != node) {
+      undisplayed += mNodeIterator.Current()->TextLength();
+      NextNode();
+    }
+    // If the current text frame starts at a non-zero content offset, then those
+    // earlier characters are also undisplayed.
+    undisplayed += frame->GetContentOffset();
+    NextNode();
+  }
+
+  // Set the frame property.
+  frame->Properties().Set(TextNodeCorrespondenceProperty(),
+                          new TextNodeCorrespondence(undisplayed));
+
+  // Remember how far into the current nsTextNode we are.
+  mNodeCharIndex = frame->GetContentEnd();
+}
+
+// ----------------------------------------------------------------------------
+// TextFrameIterator
+
+/**
+ * An iterator class for nsTextFrames that are descendants of an
+ * nsSVGTextFrame2.  The iterator can optionally track whether the
+ * current nsTextFrame is for a descendant of, or past, a given subtree
+ * content node or frame.  (This functionality is used for example by the SVG
+ * DOM text methods to get only the nsTextFrames for a particular <tspan>.)
+ *
+ * TextFrameIterator also tracks and exposes other information about the
+ * current nsTextFrame:
+ *
+ *   * how many undisplayed characters came just before it
+ *   * its position (in app units) relative to the nsSVGTextFrame2's anonymous
+ *     block frame
+ *   * what nsInlineFrame corresponding to a <textPath> element it is a
+ *     descendant of
+ *   * what computed dominant-baseline value applies to it
+ */
+class TextFrameIterator
+{
+public:
+  /**
+   * Constructs a TextFrameIterator for the specified nsSVGTextFrame2
+   * with an optional frame subtree to restrict iterated text frames to.
+   */
+  TextFrameIterator(nsSVGTextFrame2* aRoot, nsIFrame* aSubtree = nullptr)
+    : mRootFrame(aRoot),
+      mSubtree(aSubtree),
+      mCurrentFrame(aRoot),
+      mCurrentPosition(),
+      mSubtreePosition(mSubtree ? eBeforeSubtree : eWithinSubtree)
+  {
+    Init();
+  }
+
+  /**
+   * Constructs a TextFrameIterator for the specified nsSVGTextFrame2
+   * with an optional frame content subtree to restrict iterated text frames to.
+   */
+  TextFrameIterator(nsSVGTextFrame2* aRoot, nsIContent* aSubtree)
+    : mRootFrame(aRoot),
+      mSubtree(aSubtree && aSubtree != aRoot->GetContent() ?
+                 aSubtree->GetPrimaryFrame() :
+                 nullptr),
+      mCurrentFrame(aRoot),
+      mCurrentPosition(),
+      mSubtreePosition(mSubtree ? eBeforeSubtree : eWithinSubtree)
+  {
+    Init();
+  }
+
+  /**
+   * Returns the root nsSVGTextFrame2 this TextFrameIterator is iterating over.
+   */
+  nsSVGTextFrame2* Root() const
+  {
+    return mRootFrame;
+  }
+
+  /**
+   * Returns the current nsTextFrame.
+   */
+  nsTextFrame* Current() const
+  {
+    return do_QueryFrame(mCurrentFrame);
+  }
+
+  /**
+   * Returns the number of undisplayed characters in the DOM just before the
+   * current frame.
+   */
+  uint32_t UndisplayedCharacters() const;
+
+  /**
+   * Returns the current frame's position, in app units, relative to the
+   * root nsSVGTextFrame2's anonymous block frame.
+   */
+  nsPoint Position() const
+  {
+    return mCurrentPosition;
+  }
+
+  /**
+   * Advances to the next nsTextFrame and returns it.
+   */
+  nsTextFrame* Next();
+
+  /**
+   * Returns whether the iterator is within the subtree.
+   */
+  bool IsWithinSubtree() const
+  {
+    return mSubtreePosition == eWithinSubtree;
+  }
+
+  /**
+   * Returns whether the iterator is past the subtree.
+   */
+  bool IsAfterSubtree() const
+  {
+    return mSubtreePosition == eAfterSubtree;
+  }
+
+  /**
+   * Returns the frame corresponding to the <textPath> element, if we
+   * are inside one.
+   */
+  nsIFrame* TextPathFrame() const
+  {
+    return mTextPathFrames.IsEmpty() ?
+             nullptr :
+             mTextPathFrames.ElementAt(mTextPathFrames.Length() - 1);
+  }
+
+  /**
+   * Returns the current frame's computed dominant-baseline value.
+   */
+  uint8_t DominantBaseline() const
+  {
+    return mBaselines.ElementAt(mBaselines.Length() - 1);
+  }
+
+  /**
+   * Finishes the iterator.
+   */
+  void Close()
+  {
+    mCurrentFrame = nullptr;
+  }
+
+private:
+  /**
+   * Initializes the iterator and advances to the first item.
+   */
+  void Init()
+  {
+    mBaselines.AppendElement(mRootFrame->GetStyleSVGReset()->mDominantBaseline);
+    Next();
+  }
+
+  /**
+   * Pushes the specified frame's computed dominant-baseline value.
+   * If the value of the property is "auto", then the parent frame's
+   * computed value is used.
+   */
+  void PushBaseline(nsIFrame* aNextFrame);
+
+  /**
+   * Pops the current dominant-baseline off the stack.
+   */
+  void PopBaseline();
+
+  /**
+   * The root frame we are iterating through.
+   */
+  nsSVGTextFrame2* mRootFrame;
+
+  /**
+   * The frame for the subtree we are also interested in tracking.
+   */
+  nsIFrame* mSubtree;
+
+  /**
+   * The current value of the iterator.
+   */
+  nsIFrame* mCurrentFrame;
+
+  /**
+   * The position, in app units, of the current frame relative to mRootFrame.
+   */
+  nsPoint mCurrentPosition;
+
+  /**
+   * Stack of frames corresponding to <textPath> elements that are in scope
+   * for the current frame.
+   */
+  nsAutoTArray<nsIFrame*, 1> mTextPathFrames;
+
+  /**
+   * Stack of dominant-baseline values to record as we traverse through the
+   * frame tree.
+   */
+  nsAutoTArray<uint8_t, 8> mBaselines;
+
+  /**
+   * The iterator's current position relative to mSubtree.
+   */
+  SubtreePosition mSubtreePosition;
+};
+
+uint32_t
+TextFrameIterator::UndisplayedCharacters() const
+{
+  if (!mCurrentFrame) {
+    return mRootFrame->mTrailingUndisplayedCharacters;
+  }
+
+  nsTextFrame* frame = do_QueryFrame(mCurrentFrame);
+  return GetUndisplayedCharactersBeforeFrame(frame);
+}
+
+nsTextFrame*
+TextFrameIterator::Next()
+{
+  // Starting from mCurrentFrame, we do a non-recursive traversal to the next
+  // nsTextFrame beneath mRoot, updating mSubtreePosition appropriately if we
+  // encounter mSubtree.
+  if (mCurrentFrame) {
+    do {
+      nsIFrame* next = IsTextContentElement(mCurrentFrame->GetContent()) ?
+                         mCurrentFrame->GetFirstPrincipalChild() :
+                         nullptr;
+      if (next) {
+        // Descend into this frame, and accumulate its position.
+        mCurrentPosition += next->GetPosition();
+        if (next->GetContent()->Tag() == nsGkAtoms::textPath) {
+          // Record this <textPath> frame.
+          mTextPathFrames.AppendElement(next);
+        }
+        // Record the frame's baseline.
+        PushBaseline(next);
+        mCurrentFrame = next;
+        if (mCurrentFrame == mSubtree) {
+          // If the current frame is mSubtree, we have now moved into it.
+          mSubtreePosition = eWithinSubtree;
+        }
+      } else {
+        for (;;) {
+          // We want to move past the current frame.
+          if (mCurrentFrame == mRootFrame) {
+            // If we've reached the root frame, we're finished.
+            mCurrentFrame = nullptr;
+            break;
+          }
+          // Remove the current frame's position.
+          mCurrentPosition -= mCurrentFrame->GetPosition();
+          if (mCurrentFrame->GetContent()->Tag() == nsGkAtoms::textPath) {
+            // Pop off the <textPath> frame if this is a <textPath>.
+            mTextPathFrames.TruncateLength(mTextPathFrames.Length() - 1);
+          }
+          // Pop off the current baseline.
+          PopBaseline();
+          if (mCurrentFrame == mSubtree) {
+            // If this was mSubtree, we have now moved past it.
+            mSubtreePosition = eAfterSubtree;
+          }
+          next = mCurrentFrame->GetNextSibling();
+          if (next) {
+            // Moving to the next sibling.
+            mCurrentPosition += next->GetPosition();
+            if (next->GetContent()->Tag() == nsGkAtoms::textPath) {
+              // Record this <textPath> frame.
+              mTextPathFrames.AppendElement(next);
+            }
+            // Record the frame's baseline.
+            PushBaseline(next);
+            mCurrentFrame = next;
+            if (mCurrentFrame == mSubtree) {
+              // If the current frame is mSubtree, we have now moved into it.
+              mSubtreePosition = eWithinSubtree;
+            }
+            break;
+          }
+          if (mCurrentFrame == mSubtree) {
+            // If there is no next sibling frame, and the current frame is
+            // mSubtree, we have now moved past it.
+            mSubtreePosition = eAfterSubtree;
+          }
+          // Ascend out of this frame.
+          mCurrentFrame = mCurrentFrame->GetParent();
+        }
+      }
+    } while (mCurrentFrame &&
+             !IsNonEmptyTextFrame(mCurrentFrame));
+  }
+
+  return Current();
+}
+
+void
+TextFrameIterator::PushBaseline(nsIFrame* aNextFrame)
+{
+  uint8_t baseline = aNextFrame->GetStyleSVGReset()->mDominantBaseline;
+  if (baseline != NS_STYLE_DOMINANT_BASELINE_AUTO) {
+    mBaselines.AppendElement(baseline);
+  } else {
+    mBaselines.AppendElement(mBaselines[mBaselines.Length() - 1]);
+  }
+}
+
+void
+TextFrameIterator::PopBaseline()
+{
+  NS_ASSERTION(!mBaselines.IsEmpty(), "popped too many baselines");
+  mBaselines.TruncateLength(mBaselines.Length() - 1);
+}
+
+// -----------------------------------------------------------------------------
+// TextRenderedRunIterator
+
+/**
+ * Iterator for TextRenderedRun objects for the nsSVGTextFrame2.
+ */
+class TextRenderedRunIterator
+{
+public:
+  /**
+   * Values for the aFilter argument of the constructor, to indicate which frames
+   * we should be limited to iterating TextRenderedRun objects for.
+   */
+  enum RenderedRunFilter {
+    // Iterate TextRenderedRuns for all nsTextFrames.
+    eAllFrames,
+    // Iterate only TextRenderedRuns for nsTextFrames that are
+    // visibility:visible.
+    eVisibleFrames
+  };
+
+  /**
+   * Constructs a TextRenderedRunIterator with an optional frame subtree to
+   * restrict iterated rendered runs to.
+   *
+   * @param aSVGTextFrame The nsSVGTextFrame2 whose rendered runs to iterate
+   *   through.
+   * @param aFilter Indicates whether to iterate rendered runs for non-visible
+   *   nsTextFrames.
+   * @param aSubtree An optional frame subtree to restrict iterated rendered
+   *   runs to.
+   */
+  TextRenderedRunIterator(nsSVGTextFrame2* aSVGTextFrame,
+                          RenderedRunFilter aFilter = eAllFrames,
+                          nsIFrame* aSubtree = nullptr)
+    : mFrameIterator(aSVGTextFrame, aSubtree),
+      mFilter(aFilter),
+      mTextElementCharIndex(0),
+      mFrameStartTextElementCharIndex(0),
+      mFontSizeScaleFactor(aSVGTextFrame->mFontSizeScaleFactor),
+      mCurrent(First())
+  {
+  }
+
+  /**
+   * Constructs a TextRenderedRunIterator with a content subtree to restrict
+   * iterated rendered runs to.
+   *
+   * @param aSVGTextFrame The nsSVGTextFrame2 whose rendered runs to iterate
+   *   through.
+   * @param aFilter Indicates whether to iterate rendered runs for non-visible
+   *   nsTextFrames.
+   * @param aSubtree A content subtree to restrict iterated rendered runs to.
+   */
+  TextRenderedRunIterator(nsSVGTextFrame2* aSVGTextFrame,
+                          RenderedRunFilter aFilter,
+                          nsIContent* aSubtree)
+    : mFrameIterator(aSVGTextFrame, aSubtree),
+      mFilter(aFilter),
+      mTextElementCharIndex(0),
+      mFrameStartTextElementCharIndex(0),
+      mFontSizeScaleFactor(aSVGTextFrame->mFontSizeScaleFactor),
+      mCurrent(First())
+  {
+  }
+
+  /**
+   * Returns the current TextRenderedRun.
+   */
+  TextRenderedRun Current() const
+  {
+    return mCurrent;
+  }
+
+  /**
+   * Advances to the next TextRenderedRun and returns it.
+   */
+  TextRenderedRun Next();
+
+private:
+  /**
+   * Returns the root nsSVGTextFrame2 this iterator is for.
+   */
+  nsSVGTextFrame2* Root() const
+  {
+    return mFrameIterator.Root();
+  }
+
+  /**
+   * Advances to the first TextRenderedRun and returns it.
+   */
+  TextRenderedRun First();
+
+  /**
+   * The frame iterator to use.
+   */
+  TextFrameIterator mFrameIterator;
+
+  /**
+   * The filter indicating which TextRenderedRuns to return.
+   */
+  RenderedRunFilter mFilter;
+
+  /**
+   * The character index across the entire <text> element we are currently
+   * up to.
+   */
+  uint32_t mTextElementCharIndex;
+
+  /**
+   * The character index across the entire <text> for the start of the current
+   * frame.
+   */
+  uint32_t mFrameStartTextElementCharIndex;
+
+  /**
+   * The font-size scale factor we used when constructing the nsTextFrames.
+   */
+  double mFontSizeScaleFactor;
+
+  /**
+   * The current TextRenderedRun.
+   */
+  TextRenderedRun mCurrent;
+};
+
+TextRenderedRun
+TextRenderedRunIterator::Next()
+{
+  if (!mFrameIterator.Current()) {
+    // If there are no more frames, then there are no more rendered runs to
+    // return.
+    mCurrent = TextRenderedRun();
+    return mCurrent;
+  }
+
+  // The values we will use to initialize the TextRenderedRun with.
+  nsTextFrame* frame;
+  gfxPoint pt;
+  double rotate;
+  nscoord baseline;
+  uint32_t offset, length;
+  uint32_t charIndex;
+
+  // We loop, because we want to skip over rendered runs that either aren't
+  // within our subtree of interest, because they don't match the filter,
+  // or because they are hidden due to having fallen off the end of a
+  // <textPath>.
+  for (;;) {
+    if (mFrameIterator.IsAfterSubtree()) {
+      mCurrent = TextRenderedRun();
+      return mCurrent;
+    }
+
+    frame = mFrameIterator.Current();
+
+    // Get the character index for the start of this rendered run, by skipping
+    // any undisplayed characters.
+    if (frame != mCurrent.mFrame) {
+      mFrameStartTextElementCharIndex += mFrameIterator.UndisplayedCharacters();
+      mTextElementCharIndex += mFrameIterator.UndisplayedCharacters();
+    }
+    charIndex = mTextElementCharIndex;
+
+    // Get the position and rotation of the character that begins this
+    // rendered run.
+    pt = Root()->mPositions[mTextElementCharIndex].mPosition;
+    rotate = Root()->mPositions[mTextElementCharIndex].mAngle;
+
+    // Find the end of the rendered run, by looking through the
+    // nsSVGTextFrame2's positions array until we find one that is recorded
+    // as a run boundary.
+    uint32_t runStart, runEnd;  // XXX Replace runStart with mTextElementCharIndex.
+    runStart = mTextElementCharIndex;
+    runEnd = runStart + 1;
+    while (runEnd < Root()->mPositions.Length() &&
+           !Root()->mPositions[runEnd].mRunBoundary) {
+      runEnd++;
+    }
+
+    // Convert the global run start/end indexes into an offset/length into the
+    // current frame's nsTextNode.
+    offset = frame->GetContentOffset() + runStart -
+             mFrameStartTextElementCharIndex;
+    length = runEnd - runStart;
+
+    // If the end of the frame's content comes before the run boundary we found
+    // in nsSVGTextFrame2's position array, we need to shorten the rendered run.
+    uint32_t contentEnd = frame->GetContentEnd();
+    if (offset + length > contentEnd) {
+      length = contentEnd - offset;
+    }
+
+    NS_ASSERTION(offset >= uint32_t(frame->GetContentOffset()), "invalid offset");
+    NS_ASSERTION(offset + length <= contentEnd, "invalid offset or length");
+
+    // Get the frame's baseline position.
+    frame->EnsureTextRun(nsTextFrame::eInflated);
+    baseline = GetBaselinePosition(frame,
+                                   frame->GetTextRun(nsTextFrame::eInflated),
+                                   mFrameIterator.DominantBaseline());
+
+    // Trim the offset/length to remove any leading/trailing white space.
+    uint32_t untrimmedLength = length;
+    nsTextFrame::TrimmedOffsets trimmedOffsets =
+      frame->GetTrimmedOffsets(frame->GetContent()->GetText(), true);
+    TrimOffsets(offset, length, trimmedOffsets);
+
+    // Determine if we should skip this rendered run.
+    bool skip = !mFrameIterator.IsWithinSubtree() ||
+                Root()->mPositions[mTextElementCharIndex].mHidden;
+    if (mFilter == eVisibleFrames) {
+      skip = skip || !frame->GetStyleVisibility()->IsVisible();
+    }
+
+    // Update our global character index to move past the characters
+    // corresponding to this rendered run.
+    mTextElementCharIndex += untrimmedLength;
+
+    // If we have moved past the end of the current frame's content, we need to
+    // advance to the next frame.
+    if (offset + untrimmedLength >= contentEnd) {
+      mFrameIterator.Next();
+      mFrameStartTextElementCharIndex = mTextElementCharIndex;
+    }
+
+    if (!mFrameIterator.Current()) {
+      if (skip) {
+        // That was the last frame, and we skipped this rendered run.  So we
+        // have no rendered run to return.
+        mCurrent = TextRenderedRun();
+        return mCurrent;
+      }
+      break;
+    }
+
+    if (length && !skip) {
+      // Only return a rendered run if it didn't get collapsed away entirely
+      // (due to it being all white space) and if we don't want to skip it.
+      break;
+    }
+  }
+
+  mCurrent = TextRenderedRun(frame, pt, rotate, mFontSizeScaleFactor, baseline,
+                             offset, length, charIndex);
+  return mCurrent;
+}
+
+TextRenderedRun
+TextRenderedRunIterator::First()
+{
+  if (Root()->mPositions.IsEmpty()) {
+    mFrameIterator.Close();
+    return TextRenderedRun();
+  }
+  return Next();
+}
+
+// -----------------------------------------------------------------------------
+// CharIterator
+
+/**
+ * Iterator for characters within an nsSVGTextFrame2.
+ */
+class CharIterator
+{
+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.
+    eOriginal,
+    // Iterate only over characters that are not skipped per the
+    // gfxSkipCharsIterator used for the text runs.
+    eNonSkipped,
+    // terate only over characters that are the first of clusters or ligature
+    // groups.
+    eClusterAndLigatureGroupStart,
+    // Iterate only over characters that are part of a cluster or ligature
+    // group but not the first character.
+    eClusterOrLigatureGroupMiddle
+  };
+
+  /**
+   * Constructs a CharIterator.
+   *
+   * @param aSVGTextFrame The nsSVGTextFrame2 whose characters to iterate
+   *   through.
+   * @param aFilter Indicates which characters to iterate over.
+   */
+  CharIterator(nsSVGTextFrame2* aSVGTextFrame, CharacterFilter aFilter);
+
+  /**
+   * Returns whether the iterator is finished.
+   */
+  bool AtEnd() const
+  {
+    return !mFrameIterator.Current();
+  }
+
+  /**
+   * Advances to the next character.  Returns true if there was a character to
+   * advance to, and false otherwise.
+   */
+  bool Next();
+
+  /**
+   * Advances to the character with the specified index.  The index is in the
+   * space of original characters (i.e., all DOM characters under the <text>
+   * that are within valid text content elements).
+   */
+  bool AdvanceToCharacter(uint32_t aTextElementCharIndex);
+
+  /**
+   * Advances to the first matching character after the current nsTextFrame.
+   */
+  bool AdvancePastCurrentFrame();
+
+  /**
+   * Advances to the first matching character after the frames within
+   * the current <textPath>.
+   */
+  bool AdvancePastCurrentTextPathFrame();
+
+  /**
+   * Returns the nsTextFrame for the current character.
+   */
+  nsTextFrame* TextFrame() const
+  {
+    return mFrameIterator.Current();
+  }
+
+  /**
+   * Returns whether the current character is a skipped character.
+   */
+  bool IsOriginalCharSkipped() const
+  {
+    return mSkipCharsIterator.IsOriginalCharSkipped();
+  }
+
+  /**
+   * Returns whether the current character is the start of a cluster and
+   * ligature group.
+   */
+  bool IsClusterAndLigatureGroupStart() const;
+
+  /**
+   * Returns whether the current character is trimmed away when painting,
+   * due to it being leading/trailing white space.
+   */
+  bool IsOriginalCharTrimmed() const;
+
+  /**
+   * Returns whether the current character is unaddressable from the SVG glyph
+   * positioning attributes.
+   */
+  bool IsOriginalCharUnaddressable() const
+  {
+    return IsOriginalCharSkipped() || IsOriginalCharTrimmed();
+  }
+
+  /**
+   * Returns the text run for the current character.
+   */
+  gfxTextRun* TextRun() const
+  {
+    return mTextRun;
+  }
+
+  /**
+   * Returns the current character index.
+   */
+  uint32_t TextElementCharIndex() const
+  {
+    return mTextElementCharIndex;
+  }
+
+  /**
+   * Returns the character index for the start of the cluster/ligature group it
+   * is part of.
+   */
+  uint32_t GlyphStartTextElementCharIndex() const
+  {
+    return mGlyphStartTextElementCharIndex;
+  }
+
+  /**
+   * Gets the original character offsets within the nsTextNode for the
+   * cluster/ligature group the current character is a part of.
+   *
+   * @param aOriginalOffset The offset of the start of the cluster/ligature
+   *   group (output).
+   * @param aOriginalLength The length of cluster/ligature group (output).
+   */
+  void GetOriginalGlyphOffsets(uint32_t& aOriginalOffset,
+                               uint32_t& aOriginalLength) const;
+
+  /**
+   * Gets the advance, in user units, of the glyph the current character is
+   * part of.
+   *
+   * @param aContext The context to use for unit conversions.
+   */
+  gfxFloat GetGlyphAdvance(nsPresContext* aContext) const;
+
+  /**
+   * Gets the advance, in user units, of the current character.  If the
+   * character is a part of ligature, then the advance returned will be
+   * a fraction of the ligature glyph's advance.
+   *
+   * @param aContext The context to use for unit conversions.
+   */
+  gfxFloat GetAdvance(nsPresContext* aContext) const;
+
+  /**
+   * Gets the specified partial advance of the glyph the current character is
+   * part of.
+   *
+   * @param aPartOffset The index of the first character, starting from 0, of
+   *   the cluster/ligature group to measure.
+   * @param aPartLength The number of characters in the cluster/ligature group
+   *   to measure.
+   * @param aContext The context to use for unit conversions.
+   */
+  gfxFloat GetGlyphPartialAdvance(uint32_t aPartOffset, uint32_t aPartLength,
+                                  nsPresContext* aContext) const;
+
+  /**
+   * Returns the frame corresponding to the <textPath> that the current
+   * character is within.
+   */
+  nsIFrame* TextPathFrame() const
+  {
+    return mFrameIterator.TextPathFrame();
+  }
+
+private:
+  /**
+   * Advances to the next character without checking it against the filter.
+   * Returns true if there was a next character to advance to, or false
+   * otherwise.
+   */
+  bool NextCharacter();
+
+  /**
+   * Returns whether the current character matches the filter.
+   */
+  bool MatchesFilter() const;
+
+  /**
+   * The filter to use.
+   */
+  CharacterFilter mFilter;
+
+  /**
+   * The iterator for text frames.
+   */
+  TextFrameIterator mFrameIterator;
+
+  /**
+   * A gfxSkipCharsIterator for the text frame the current character is
+   * a part of.
+   */
+  gfxSkipCharsIterator mSkipCharsIterator;
+
+  // Cache for information computed by IsOriginalCharTrimmed.
+  mutable nsTextFrame* mFrameForTrimCheck;
+  mutable uint32_t mTrimmedOffset;
+  mutable uint32_t mTrimmedLength;
+
+  /**
+   * The text run the current character is a part of.
+   */
+  gfxTextRun* mTextRun;
+
+  /**
+   * The current character's index.
+   */
+  uint32_t mTextElementCharIndex;
+
+  /**
+   * The index of the character that starts the cluster/ligature group the
+   * current character is a part of.
+   */
+  uint32_t mGlyphStartTextElementCharIndex;
+};
+
+CharIterator::CharIterator(nsSVGTextFrame2* aSVGTextFrame,
+                           CharIterator::CharacterFilter aFilter)
+  : mFilter(aFilter),
+    mFrameIterator(aSVGTextFrame),
+    mFrameForTrimCheck(nullptr),
+    mTrimmedOffset(0),
+    mTrimmedLength(0),
+    mTextElementCharIndex(0),
+    mGlyphStartTextElementCharIndex(0)
+{
+  if (!AtEnd()) {
+    mSkipCharsIterator = TextFrame()->EnsureTextRun(nsTextFrame::eInflated);
+    mTextRun = TextFrame()->GetTextRun(nsTextFrame::eInflated);
+    mTextElementCharIndex = mFrameIterator.UndisplayedCharacters();
+    if (!MatchesFilter()) {
+      Next();
+    }
+  }
+}
+
+bool
+CharIterator::Next()
+{
+  while (NextCharacter()) {
+    if (MatchesFilter()) {
+      return true;
+    }
+  }
+  return false;
+}
+
+bool
+CharIterator::AdvanceToCharacter(uint32_t aTextElementCharIndex)
+{
+  while (mTextElementCharIndex < aTextElementCharIndex) {
+    if (!Next()) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool
+CharIterator::AdvancePastCurrentFrame()
+{
+  // XXX Can do this better than one character at a time if it matters.
+  nsTextFrame* currentFrame = TextFrame();
+  do {
+    if (!Next()) {
+      return false;
+    }
+  } while (TextFrame() == currentFrame);
+  return true;
+}
+
+bool
+CharIterator::AdvancePastCurrentTextPathFrame()
+{
+  nsIFrame* currentTextPathFrame = TextPathFrame();
+  NS_ASSERTION(currentTextPathFrame,
+               "expected AdvancePastCurrentTextPathFrame to be called only "
+               "within a text path frame");
+  do {
+    if (!AdvancePastCurrentFrame()) {
+      return false;
+    }
+  } while (TextPathFrame() == currentTextPathFrame);
+  return true;
+}
+
+bool
+CharIterator::IsClusterAndLigatureGroupStart() const
+{
+  return mTextRun->IsLigatureGroupStart(mSkipCharsIterator.GetSkippedOffset()) &&
+         mTextRun->IsClusterStart(mSkipCharsIterator.GetSkippedOffset());
+}
+
+bool
+CharIterator::IsOriginalCharTrimmed() const
+{
+  if (mFrameForTrimCheck != TextFrame()) {
+    // Since we do a lot of trim checking, we cache the trimmed offsets and
+    // lengths while we are in the same frame.
+    mFrameForTrimCheck = TextFrame();
+    uint32_t offset = mFrameForTrimCheck->GetContentOffset();
+    uint32_t length = mFrameForTrimCheck->GetContentLength();
+    nsIContent* content = mFrameForTrimCheck->GetContent();
+    nsTextFrame::TrimmedOffsets trim =
+      mFrameForTrimCheck->GetTrimmedOffsets(content->GetText(), true);
+    TrimOffsets(offset, length, trim);
+    mTrimmedOffset = offset;
+    mTrimmedLength = length;
+  }
+
+  // A character is trimmed if it is outside the mTrimmedOffset/mTrimmedLength
+  // range and it is not a significant newline character.
+  uint32_t index = mSkipCharsIterator.GetOriginalOffset();
+  return !((index >= mTrimmedOffset &&
+            index < mTrimmedOffset + mTrimmedLength) ||
+           (index >= mTrimmedOffset + mTrimmedLength &&
+            mFrameForTrimCheck->GetStyleText()->NewlineIsSignificant() &&
+            mFrameForTrimCheck->GetContent()->GetText()->CharAt(index) == '\n'));
+}
+
+void
+CharIterator::GetOriginalGlyphOffsets(uint32_t& aOriginalOffset,
+                                      uint32_t& aOriginalLength) const
+{
+  gfxSkipCharsIterator it = TextFrame()->EnsureTextRun(nsTextFrame::eInflated);
+  it.SetOriginalOffset(mSkipCharsIterator.GetOriginalOffset() -
+                         (mTextElementCharIndex - mGlyphStartTextElementCharIndex));
+
+  while (it.GetSkippedOffset() > 0 &&
+         (!mTextRun->IsClusterStart(it.GetSkippedOffset()) ||
+          !mTextRun->IsLigatureGroupStart(it.GetSkippedOffset()))) {
+    it.AdvanceSkipped(-1);
+  }
+
+  aOriginalOffset = it.GetOriginalOffset();
+
+  // Find the end of the cluster/ligature group.
+  it.SetOriginalOffset(mSkipCharsIterator.GetOriginalOffset());
+  do {
+    it.AdvanceSkipped(1);
+  } while (it.GetSkippedOffset() < mTextRun->GetLength() &&
+           (!mTextRun->IsClusterStart(it.GetSkippedOffset()) ||
+            !mTextRun->IsLigatureGroupStart(it.GetSkippedOffset())));
+
+  aOriginalLength = it.GetOriginalOffset() - aOriginalOffset;
+}
+
+gfxFloat
+CharIterator::GetGlyphAdvance(nsPresContext* aContext) const
+{
+  uint32_t offset, length;
+  GetOriginalGlyphOffsets(offset, length);
+
+  gfxSkipCharsIterator it = TextFrame()->EnsureTextRun(nsTextFrame::eInflated);
+  ConvertOriginalToSkipped(it, offset, length);
+
+  float cssPxPerDevPx = aContext->
+    AppUnitsToFloatCSSPixels(aContext->AppUnitsPerDevPixel());
+
+  gfxFloat advance = mTextRun->GetAdvanceWidth(offset, length, nullptr);
+  return aContext->AppUnitsToGfxUnits(advance) * cssPxPerDevPx;
+}
+
+gfxFloat
+CharIterator::GetAdvance(nsPresContext* aContext) const
+{
+  float cssPxPerDevPx = aContext->
+    AppUnitsToFloatCSSPixels(aContext->AppUnitsPerDevPixel());
+
+  gfxFloat advance =
+    mTextRun->GetAdvanceWidth(mSkipCharsIterator.GetSkippedOffset(), 1, nullptr);
+  return aContext->AppUnitsToGfxUnits(advance) * cssPxPerDevPx;
+}
+
+gfxFloat
+CharIterator::GetGlyphPartialAdvance(uint32_t aPartOffset, uint32_t aPartLength,
+                                     nsPresContext* aContext) const
+{
+  uint32_t offset, length;
+  GetOriginalGlyphOffsets(offset, length);
+
+  NS_ASSERTION(aPartOffset <= length && aPartOffset + aPartLength <= length,
+               "invalid aPartOffset / aPartLength values");
+  offset += aPartOffset;
+  length = aPartLength;
+
+  gfxSkipCharsIterator it = TextFrame()->EnsureTextRun(nsTextFrame::eInflated);
+  ConvertOriginalToSkipped(it, offset, length);
+
+  float cssPxPerDevPx = aContext->
+    AppUnitsToFloatCSSPixels(aContext->AppUnitsPerDevPixel());
+
+  gfxFloat advance = mTextRun->GetAdvanceWidth(offset, length, nullptr);
+  return aContext->AppUnitsToGfxUnits(advance) * cssPxPerDevPx;
+}
+
+bool
+CharIterator::NextCharacter()
+{
+  mTextElementCharIndex++;
+
+  // Advance within the current text run.
+  mSkipCharsIterator.AdvanceOriginal(1);
+  if (mSkipCharsIterator.GetOriginalOffset() < TextFrame()->GetContentEnd()) {
+    // We're still within the part of the text run for the current text frame.
+    if (!IsOriginalCharSkipped() && IsClusterAndLigatureGroupStart()) {
+      // If this is the start of a glyph, record it.
+      mGlyphStartTextElementCharIndex = mTextElementCharIndex;
+    }
+    return true;
+  }
+
+  // Advance to the next frame.
+  mFrameIterator.Next();
+
+  // Skip any undisplayed characters.
+  mTextElementCharIndex += mFrameIterator.UndisplayedCharacters();
+  if (!TextFrame()) {
+    // We're at the end.
+    mSkipCharsIterator = gfxSkipCharsIterator();
+    return false;
+  }
+
+  mSkipCharsIterator = TextFrame()->EnsureTextRun(nsTextFrame::eInflated);
+  mTextRun = TextFrame()->GetTextRun(nsTextFrame::eInflated);
+  if (!IsOriginalCharSkipped() && IsClusterAndLigatureGroupStart()) {
+    // If this is the start of a glyph, record it.
+    mGlyphStartTextElementCharIndex = mTextElementCharIndex;
+  }
+  return true;
+}
+
+bool
+CharIterator::MatchesFilter() const
+{
+  if (mFilter == eOriginal) {
+    return true;
+  }
+
+  if (IsOriginalCharSkipped()) {
+    return false;
+  }
+
+  if (mFilter == eNonSkipped) {
+    return true;
+  }
+
+  return (mFilter == eClusterAndLigatureGroupStart) ==
+         IsClusterAndLigatureGroupStart();
+}
+
+/**
+ * An nsCharClipDisplayItem that obtains its left and right clip edges from a
+ * TextRenderedRun object.
+ */
+class SVGCharClipDisplayItem : public nsCharClipDisplayItem {
+public:
+  SVGCharClipDisplayItem(const TextRenderedRun& aRun)
+    : nsCharClipDisplayItem(aRun.mFrame)
+  {
+    aRun.GetClipEdges(mLeftEdge, mRightEdge);
+  }
+
+  NS_DISPLAY_DECL_NAME("SVGText", TYPE_TEXT)
+};
+
+/**
+ * Text frame draw callback class that paints the text and text decoration parts
+ * of an nsTextFrame using SVG painting properties, and selection backgrounds
+ * and decorations as they would normally.
+ *
+ * An instance of this class is passed to nsTextFrame::PaintText if painting
+ * cannot be done directly (e.g. if we are using an SVG pattern fill, stroking
+ * the text, etc.).
+ */
+class SVGTextDrawPathCallbacks : public nsTextFrame::DrawPathCallbacks
+{
+public:
+  /**
+   * Constructs an SVGTextDrawPathCallbacks.
+   *
+   * @param aContext The context to use for painting.
+   * @param aFrame The nsTextFrame to paint.
+   * @param aCanvasTM The transformation matrix to set when painting; this
+   *   should be the FOR_OUTERSVG_TM canvas TM of the text, so that
+   *   paint servers are painted correctly.
+   */
+  SVGTextDrawPathCallbacks(nsRenderingContext* aContext,
+                           nsTextFrame* aFrame,
+                           const gfxMatrix& aCanvasTM)
+    : gfx(aContext->ThebesContext()),
+      mRenderMode(SVGAutoRenderState::GetRenderMode(aContext)),
+      mFrame(aFrame),
+      mCanvasTM(aCanvasTM)
+  {
+  }
+
+  void NotifyBeforeText(nscolor aColor);
+  void NotifyGlyphPathEmitted();
+  void NotifyAfterText();
+  void NotifyBeforeSelectionBackground(nscolor aColor);
+  void NotifySelectionBackgroundPathEmitted();
+  void NotifyBeforeDecorationLine(nscolor aColor);
+  void NotifyDecorationLinePathEmitted();
+  void NotifyBeforeSelectionDecorationLine(nscolor aColor);
+  void NotifySelectionDecorationLinePathEmitted();
+
+private:
+  void FillWithOpacity();
+
+  void SetupContext();
+
+  /**
+   * Paints a piece of text geometry.  This is called when glyphs
+   * or text decorations have been emitted to the gfxContext.
+   */
+  void HandleTextGeometry();
+
+  /**
+   * Sets the gfxContext paint to the appropriate color or pattern
+   * for filling text geometry.
+   */
+  bool SetFillColor();
+
+  /**
+   * Fills and strokes a piece of text geometry.
+   */
+  void FillAndStroke();
+
+  gfxContext* gfx;
+  uint16_t mRenderMode;
+  nsTextFrame* mFrame;
+  const gfxMatrix& mCanvasTM;
+
+  /**
+   * The color that we were last told from one of the path callback functions.
+   * This color can be the special NS_SAME_AS_FOREGROUND_COLOR,
+   * NS_40PERCENT_FOREGROUND_COLOR and NS_TRANSPARENT colors when we are
+   * painting selections or IME decorations.
+   */
+  nscolor mColor;
+};
+
+void
+SVGTextDrawPathCallbacks::NotifyBeforeText(nscolor aColor)
+{
+  mColor = aColor;
+  SetupContext();
+  gfx->NewPath();
+}
+
+void
+SVGTextDrawPathCallbacks::NotifyGlyphPathEmitted()
+{
+  HandleTextGeometry();
+  gfx->NewPath();
+}
+
+void
+SVGTextDrawPathCallbacks::NotifyAfterText()
+{
+  gfx->Restore();
+}
+
+void
+SVGTextDrawPathCallbacks::NotifyBeforeSelectionBackground(nscolor aColor)
+{
+  if (mRenderMode != SVGAutoRenderState::NORMAL) {
+    // Don't paint selection backgrounds when in a clip path.
+    return;
+  }
+
+  mColor = aColor;
+  gfx->Save();
+}
+
+void
+SVGTextDrawPathCallbacks::NotifySelectionBackgroundPathEmitted()
+{
+  if (mRenderMode != SVGAutoRenderState::NORMAL) {
+    // Don't paint selection backgrounds when in a clip path.
+    return;
+  }
+
+  if (SetFillColor()) {
+    FillWithOpacity();
+  }
+  gfx->Restore();
+}
+
+void
+SVGTextDrawPathCallbacks::NotifyBeforeDecorationLine(nscolor aColor)
+{
+  mColor = aColor;
+  SetupContext();
+}
+
+void
+SVGTextDrawPathCallbacks::NotifyDecorationLinePathEmitted()
+{
+  HandleTextGeometry();
+  gfx->NewPath();
+  gfx->Restore();
+}
+
+void
+SVGTextDrawPathCallbacks::NotifyBeforeSelectionDecorationLine(nscolor aColor)
+{
+  if (mRenderMode != SVGAutoRenderState::NORMAL) {
+    // Don't paint selection decorations when in a clip path.
+    return;
+  }
+
+  mColor = aColor;
+  gfx->Save();
+}
+
+void
+SVGTextDrawPathCallbacks::NotifySelectionDecorationLinePathEmitted()
+{
+  if (mRenderMode != SVGAutoRenderState::NORMAL) {
+    // Don't paint selection decorations when in a clip path.
+    return;
+  }
+
+  FillAndStroke();
+  gfx->Restore();
+}
+
+void
+SVGTextDrawPathCallbacks::FillWithOpacity()
+{
+  gfx->FillWithOpacity(mColor == NS_40PERCENT_FOREGROUND_COLOR ? 0.4 : 1.0);
+}
+
+void
+SVGTextDrawPathCallbacks::SetupContext()
+{
+  gfx->Save();
+
+  // XXX This is copied from nsSVGGlyphFrame::Render, but cairo doesn't actually
+  // seem to do anything with the antialias mode.  So we can perhaps remove it,
+  // or make SetAntialiasMode set cairo text antialiasing too.
+  switch (mFrame->GetStyleSVG()->mTextRendering) {
+  case NS_STYLE_TEXT_RENDERING_OPTIMIZESPEED:
+    gfx->SetAntialiasMode(gfxContext::MODE_ALIASED);
+    break;
+  default:
+    gfx->SetAntialiasMode(gfxContext::MODE_COVERAGE);
+    break;
+  }
+}
+
+void
+SVGTextDrawPathCallbacks::HandleTextGeometry()
+{
+  if (mRenderMode != SVGAutoRenderState::NORMAL) {
+    // We're in a clip path.
+    if (mFrame->GetStyleSVG()->mClipRule == NS_STYLE_FILL_RULE_EVENODD)
+      gfx->SetFillRule(gfxContext::FILL_RULE_EVEN_ODD);
+    else
+      gfx->SetFillRule(gfxContext::FILL_RULE_WINDING);
+
+    if (mRenderMode == SVGAutoRenderState::CLIP_MASK) {
+      gfx->SetColor(gfxRGBA(1.0f, 1.0f, 1.0f, 1.0f));
+      gfx->Fill();
+    }
+  } else {
+    // Normal painting.
+    gfxContextMatrixAutoSaveRestore saveMatrix(gfx);
+    gfx->SetMatrix(mCanvasTM);
+
+    FillAndStroke();
+  }
+}
+
+bool
+SVGTextDrawPathCallbacks::SetFillColor()
+{
+  if (mColor == NS_SAME_AS_FOREGROUND_COLOR ||
+      mColor == NS_40PERCENT_FOREGROUND_COLOR) {
+    return nsSVGUtils::SetupCairoFillPaint(mFrame, gfx);
+  }
+
+  if (mColor == NS_TRANSPARENT) {
+    return false;
+  }
+
+  gfx->SetColor(gfxRGBA(mColor));
+  return true;
+}
+
+void
+SVGTextDrawPathCallbacks::FillAndStroke()
+{
+  bool pushedGroup = false;
+  if (mColor == NS_40PERCENT_FOREGROUND_COLOR) {
+    pushedGroup = true;
+    gfx->PushGroup(gfxASurface::CONTENT_COLOR_ALPHA);
+  }
+
+  if (SetFillColor()) {
+    gfx->Fill();
+  }
+
+  if (mColor == NS_SAME_AS_FOREGROUND_COLOR ||
+      mColor == NS_40PERCENT_FOREGROUND_COLOR) {
+    // Don't paint the stroke when we are filling with a selection color.
+    if (nsSVGUtils::SetupCairoStroke(mFrame, gfx)) {
+      gfx->Stroke();
+    }
+  }
+
+  if (pushedGroup) {
+    gfx->PopGroupToSource();
+    gfx->Paint(0.4);
+  }
+}
+
+}
+
+// ============================================================================
+// nsSVGTextFrame2
+
+// ----------------------------------------------------------------------------
+// Display list item
+
+class nsDisplaySVGText : public nsDisplayItem {
+public:
+  nsDisplaySVGText(nsDisplayListBuilder* aBuilder,
+                   nsSVGTextFrame2* aFrame)
+    : nsDisplayItem(aBuilder, aFrame)
+  {
+    MOZ_COUNT_CTOR(nsDisplaySVGText);
+    NS_ABORT_IF_FALSE(aFrame, "Must have a frame!");
+  }
+#ifdef NS_BUILD_REFCNT_LOGGING
+  virtual ~nsDisplaySVGText() {
+    MOZ_COUNT_DTOR(nsDisplaySVGText);
+  }
+#endif
+
+  NS_DISPLAY_DECL_NAME("nsDisplaySVGText", TYPE_SVG_TEXT)
+
+  virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+                       HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames);
+  virtual void Paint(nsDisplayListBuilder* aBuilder,
+                     nsRenderingContext* aCtx);
+};
+
+void
+nsDisplaySVGText::HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+                          HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames)
+{
+  nsSVGTextFrame2 *frame = static_cast<nsSVGTextFrame2*>(mFrame);
+  nsPoint pointRelativeToReferenceFrame = aRect.Center();
+  // ToReferenceFrame() includes frame->GetPosition(), our user space position.
+  nsPoint userSpacePt = pointRelativeToReferenceFrame -
+                          (ToReferenceFrame() - frame->GetPosition());
+  if (frame->GetFrameForPoint(userSpacePt)) {
+    aOutFrames->AppendElement(frame);
+  }
+}
+
+void
+nsDisplaySVGText::Paint(nsDisplayListBuilder* aBuilder,
+                        nsRenderingContext* aCtx)
+{
+  // ToReferenceFrame includes our mRect offset, but painting takes
+  // account of that too. To avoid double counting, we subtract that
+  // here.
+  nsPoint offset = ToReferenceFrame() - mFrame->GetPosition();
+
+  aCtx->PushState();
+  aCtx->Translate(offset);
+  static_cast<nsSVGTextFrame2*>(mFrame)->PaintSVG(aCtx, nullptr);
+  aCtx->PopState();
+}
+
+// ---------------------------------------------------------------------
+// nsQueryFrame methods
+
+NS_QUERYFRAME_HEAD(nsSVGTextFrame2)
+  NS_QUERYFRAME_ENTRY(nsSVGTextFrame2)
+NS_QUERYFRAME_TAIL_INHERITING(nsSVGTextFrame2Base)
+
+// ---------------------------------------------------------------------
+// Implementation
+
+nsIFrame*
+NS_NewSVGTextFrame2(nsIPresShell* aPresShell, nsStyleContext* aContext)
+{
+  return new (aPresShell) nsSVGTextFrame2(aContext);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsSVGTextFrame2)
+
+// ---------------------------------------------------------------------
+// nsIFrame methods
+
+NS_IMETHODIMP
+nsSVGTextFrame2::Init(nsIContent* aContent,
+                      nsIFrame* aParent,
+                      nsIFrame* aPrevInFlow)
+{
+  NS_ASSERTION(aContent->IsSVG(nsGkAtoms::text), "Content is not an SVG text");
+
+  nsresult rv = nsSVGTextFrame2Base::Init(aContent, aParent, aPrevInFlow);
+  AddStateBits((aParent->GetStateBits() &
+                (NS_STATE_SVG_NONDISPLAY_CHILD | NS_STATE_SVG_CLIPPATH_CHILD)) |
+               NS_FRAME_SVG_LAYOUT | NS_FRAME_IS_SVG_TEXT);
+
+  mMutationObserver.StartObserving(this);
+  return rv;
+}
+
+NS_IMETHODIMP
+nsSVGTextFrame2::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+                                  const nsRect& aDirtyRect,
+                                  const nsDisplayListSet& aLists)
+{
+  NS_ASSERTION(!NS_SUBTREE_DIRTY(this), "reflow should have happened");
+  return aLists.Content()->AppendNewToTop(
+           new (aBuilder) nsDisplaySVGText(aBuilder, this));
+}
+
+NS_IMETHODIMP
+nsSVGTextFrame2::AttributeChanged(int32_t aNameSpaceID,
+                                  nsIAtom* aAttribute,
+                                  int32_t aModType)
+{
+  if (aNameSpaceID != kNameSpaceID_None)
+    return NS_OK;
+
+  if (aAttribute == nsGkAtoms::transform) {
+    NotifySVGChanged(TRANSFORM_CHANGED);
+  } else if (IsGlyphPositioningAttribute(aAttribute)) {
+    NotifyGlyphMetricsChange();
+  }
+
+  return NS_OK;
+}
+
+nsIAtom *
+nsSVGTextFrame2::GetType() const
+{
+  return nsGkAtoms::svgTextFrame2;
+}
+
+NS_IMPL_ISUPPORTS1(nsSVGTextFrame2::MutationObserver, nsIMutationObserver)
+
+void
+nsSVGTextFrame2::MutationObserver::ContentAppended(nsIDocument *aDocument,
+                                                   nsIContent *aContainer,
+                                                   nsIContent *aFirstNewContent,
+                                                   int32_t aNewIndexInContainer)
+{
+  mFrame->NotifyGlyphMetricsChange();
+}
+
+void
+nsSVGTextFrame2::MutationObserver::ContentInserted(nsIDocument *aDocument,
+                                        nsIContent *aContainer,
+                                        nsIContent *aChild,
+                                        int32_t aIndexInContainer)
+{
+  mFrame->NotifyGlyphMetricsChange();
+}
+
+void
+nsSVGTextFrame2::MutationObserver::ContentRemoved(nsIDocument *aDocument,
+                                       nsIContent *aContainer,
+                                       nsIContent *aChild,
+                                       int32_t aIndexInContainer,
+                                       nsIContent *aPreviousSibling)
+{
+  mFrame->NotifyGlyphMetricsChange();
+}
+
+NS_IMETHODIMP
+nsSVGTextFrame2::Reflow(nsPresContext*           aPresContext,
+                        nsHTMLReflowMetrics&     aDesiredSize,
+                        const nsHTMLReflowState& aReflowState,
+                        nsReflowStatus&          aStatus)
+{
+  NS_ABORT_IF_FALSE(!(GetStateBits() & NS_STATE_SVG_NONDISPLAY_CHILD),
+                    "Should not have been called");
+
+  // Only InvalidateAndScheduleReflowSVG marks us with NS_FRAME_IS_DIRTY,
+  // so if that bit is still set we still have a resize pending. If we hit
+  // this assertion, then we should get the presShell to skip reflow roots
+  // that have a dirty parent since a reflow is going to come via the
+  // reflow root's parent anyway.
+  NS_ASSERTION(!(GetStateBits() & NS_FRAME_IS_DIRTY),
+               "Reflowing while a resize is pending is wasteful");
+
+  // ReflowSVG makes sure mRect is up to date before we're called.
+
+  NS_ASSERTION(!aReflowState.parentReflowState,
+               "should only get reflow from being reflow root");
+  NS_ASSERTION(aReflowState.ComputedWidth() == GetSize().width &&
+               aReflowState.ComputedHeight() == GetSize().height,
+               "reflow roots should be reflowed at existing size and "
+               "svg.css should ensure we have no padding/border/margin");
+
+  DoReflow(false);
+
+  aDesiredSize.width = aReflowState.ComputedWidth();
+  aDesiredSize.height = aReflowState.ComputedHeight();
+  aDesiredSize.SetOverflowAreasToDesiredBounds();
+  aStatus = NS_FRAME_COMPLETE;
+
+  return NS_OK;
+}
+
+void
+nsSVGTextFrame2::InvalidateInternal(const nsRect& aDamageRect,
+                                    nscoord aX, nscoord aY,
+                                    nsIFrame* aForChild, uint32_t aFlags)
+{
+  // This is called by our descendants when they change.
+  // We just invalidate our entire area for now.
+  nsSVGUtils::InvalidateBounds
+    (this, nsSVGUtils::OuterSVGIsCallingReflowSVG(this), nullptr, aFlags);
+}
+
+//----------------------------------------------------------------------
+// nsISVGChildFrame methods
+
+void
+nsSVGTextFrame2::NotifySVGChanged(uint32_t aFlags)
+{
+  NS_ABORT_IF_FALSE(aFlags & (TRANSFORM_CHANGED | COORD_CONTEXT_CHANGED),
+                    "Invalidation logic may need adjusting");
+
+  bool needNewBounds = false;
+  bool needGlyphMetricsUpdate = false;
+  bool needNewCanvasTM = false;
+
+  if (aFlags & COORD_CONTEXT_CHANGED) {
+    needGlyphMetricsUpdate = true;
+  }
+
+  if (aFlags & TRANSFORM_CHANGED) {
+    needNewCanvasTM = true;
+    if (mCanvasTM && mCanvasTM->IsSingular()) {
+      // We won't have calculated the glyph positions correctly.
+      needNewBounds = true;
+      needGlyphMetricsUpdate = true;
+    }
+  }
+
+  if (needNewBounds) {
+    // Ancestor changes can't affect how we render from the perspective of
+    // any rendering observers that we may have, so we don't need to
+    // invalidate them. We also don't need to invalidate ourself, since our
+    // changed ancestor will have invalidated its entire area, which includes
+    // our area.
+    nsSVGUtils::ScheduleReflowSVG(this);
+  }
+
+  if (needGlyphMetricsUpdate) {
+    // If we are positioned using percentage values we need to update our
+    // position whenever our viewport's dimensions change.  But only do this if
+    // we have been reflowed once, otherwise the glyph positioning will be
+    // wrong.  (We need to wait until bidi reordering has been done.)
+    if (!(mState & NS_FRAME_FIRST_REFLOW)) {
+      NotifyGlyphMetricsChange();
+    }
+  }
+
+  if (needNewCanvasTM) {
+    // Do this after calling InvalidateAndScheduleReflowSVG in case we
+    // change the code and it needs to use it.
+    mCanvasTM = nullptr;
+  }
+}
+
+/**
+ * Gets the offset into a DOM node that the specified caret is positioned at.
+ */
+static int32_t
+GetCaretOffset(nsCaret* aCaret)
+{
+  nsCOMPtr<nsISelection> selection = aCaret->GetCaretDOMSelection();
+  if (!selection) {
+    return -1;
+  }
+
+  int32_t offset = -1;
+  selection->GetAnchorOffset(&offset);
+  return offset;
+}
+
+/**
+ * Returns whether the caret should be painted for a given TextRenderedRun
+ * by checking whether the caret is in the range covered by the rendered run.
+ *
+ * @param aThisRun The TextRenderedRun to be painted.
+ * @param aCaret The caret.
+ */
+static bool
+ShouldPaintCaret(const TextRenderedRun& aThisRun, nsCaret* aCaret)
+{
+  int32_t caretOffset = GetCaretOffset(aCaret);
+
+  if (caretOffset < 0) {
+    return false;
+  }
+
+  if (uint32_t(caretOffset) >= aThisRun.mTextFrameContentOffset &&
+      uint32_t(caretOffset) < aThisRun.mTextFrameContentOffset +
+                                aThisRun.mTextFrameContentLength) {
+    return true;
+  }
+
+  return false;
+}
+
+NS_IMETHODIMP
+nsSVGTextFrame2::PaintSVG(nsRenderingContext* aContext,
+                          const nsIntRect *aDirtyRect)
+{
+  nsIFrame* kid = GetFirstPrincipalChild();
+  if (!kid)
+    return NS_OK;
+
+  gfxContext *gfx = aContext->ThebesContext();
+  gfxMatrix initialMatrix = gfx->CurrentMatrix();
+
+  AutoCanvasTMForMarker autoCanvasTMFor(this, FOR_PAINTING);
+
+  if (mState & NS_STATE_SVG_NONDISPLAY_CHILD) {
+    // Text frames inside <clipPath>, <mask>, etc. will never have had
+    // ReflowSVG called on them, so call UpdateGlyphPositioning to do this now.
+    UpdateGlyphPositioning(true);
+  } else {
+    NS_ASSERTION(!NS_SUBTREE_DIRTY(this), "reflow should have happened");
+  }
+
+  gfxMatrix canvasTM = GetCanvasTM(FOR_PAINTING);
+  if (canvasTM.IsSingular()) {
+    NS_WARNING("Can't render text element!");
+    return NS_ERROR_FAILURE;
+  }
+
+  gfxMatrix matrixForPaintServers(canvasTM);
+  matrixForPaintServers.Multiply(initialMatrix);
+
+  nsPresContext* presContext = PresContext();
+
+  // Check if we need to draw anything.
+  if (aDirtyRect) {
+    NS_ASSERTION(!NS_SVGDisplayListPaintingEnabled() ||
+                 (mState & NS_STATE_SVG_NONDISPLAY_CHILD),
+                 "Display lists handle dirty rect intersection test");
+    nsRect dirtyRect(aDirtyRect->x, aDirtyRect->y,
+                     aDirtyRect->width, aDirtyRect->height);
+
+    gfxFloat appUnitsPerDevPixel = presContext->AppUnitsPerDevPixel();
+    gfxRect frameRect(mRect.x / appUnitsPerDevPixel,
+                      mRect.y / appUnitsPerDevPixel,
+                      mRect.width / appUnitsPerDevPixel,
+                      mRect.height / appUnitsPerDevPixel);
+
+    nsRect canvasRect = nsLayoutUtils::RoundGfxRectToAppRect(
+        GetCanvasTM(FOR_OUTERSVG_TM).TransformBounds(frameRect), 1);
+    if (!canvasRect.Intersects(dirtyRect)) {
+      return NS_OK;
+    }
+  }
+
+  // SVG paints in CSS px, but normally frames paint in dev pixels. Here we
+  // multiply a CSS-px-to-dev-pixel factor onto canvasTM so our children paint
+  // correctly.
+  float cssPxPerDevPx = presContext->
+    AppUnitsToFloatCSSPixels(presContext->AppUnitsPerDevPixel());
+  gfxMatrix canvasTMForChildren = canvasTM;
+  canvasTMForChildren.Scale(cssPxPerDevPx, cssPxPerDevPx);
+  initialMatrix.Scale(1 / cssPxPerDevPx, 1 / cssPxPerDevPx);
+
+  gfxContextAutoSaveRestore save(gfx);
+  gfx->NewPath();
+  gfx->Multiply(canvasTMForChildren);
+  gfxMatrix currentMatrix = gfx->CurrentMatrix();
+
+  nsRefPtr<nsCaret> caret = presContext->PresShell()->GetCaret();
+  nsIFrame* caretFrame = caret->GetCaretFrame();
+
+  TextRenderedRunIterator it(this, TextRenderedRunIterator::eVisibleFrames);
+  TextRenderedRun run = it.Current();
+  while (run.mFrame) {
+    nsTextFrame* frame = run.mFrame;
+
+    // Determine how much of the left and right edges of the text frame we
+    // need to ignore.
+    SVGCharClipDisplayItem item(run);
+
+    // Set up the transform for painting the text frame for the substring
+    // indicated by the run.
+    gfxMatrix runTransform =
+      run.GetTransformFromUserSpaceForPainting(presContext, item);
+    runTransform.Multiply(currentMatrix);
+    gfx->SetMatrix(runTransform);
+
+    nsRect frameRect = frame->GetVisualOverflowRect();
+    if (ShouldRenderAsPath(aContext, frame)) {
+      SVGTextDrawPathCallbacks callbacks(aContext, frame, matrixForPaintServers);
+      frame->PaintText(aContext, nsPoint(), frameRect, item, &callbacks);
+    } else {
+      frame->PaintText(aContext, nsPoint(), frameRect, item, nullptr);
+    }
+
+    if (frame == caretFrame && ShouldPaintCaret(run, caret)) {
+      // XXX Should we be looking at the fill/stroke colours to paint the
+      // caret with, rather than using the color property?
+      caret->PaintCaret(nullptr, aContext, frame, nsPoint());
+      gfx->NewPath();
+    }
+
+    run = it.Next();
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP_(nsRect)
+nsSVGTextFrame2::GetCoveredRegion()
+{
+  return nsSVGUtils::TransformFrameRectToOuterSVG(
+           mRect, GetCanvasTM(FOR_OUTERSVG_TM), PresContext());
+}
+
+void
+nsSVGTextFrame2::ReflowSVG()
+{
+  NS_ASSERTION(nsSVGUtils::OuterSVGIsCallingReflowSVG(this),
+               "This call is probaby a wasteful mistake");
+
+  NS_ABORT_IF_FALSE(!(GetStateBits() & NS_STATE_SVG_NONDISPLAY_CHILD),
+                    "ReflowSVG mechanism not designed for this");
+
+  if (!nsSVGUtils::NeedsReflowSVG(this)) {
+    NS_ASSERTION(!mPositioningDirty, "How did this happen?");
+    return;
+  }
+
+  // UpdateGlyphPositioning may have been called under DOM calls and set
+  // mPositioningDirty to false. We may now have better positioning, though, so
+  // set it to true so that UpdateGlyphPositioning will do its work.
+  mPositioningDirty = true;
+
+  // UpdateGlyphPositioning will call DoReflow if necessary.
+  UpdateGlyphPositioning(false);
+
+  nsPresContext* presContext = PresContext();
+
+  gfxRect r;
+  TextRenderedRunIterator it(this, TextRenderedRunIterator::eAllFrames);
+  for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) {
+    uint32_t runFlags = 0;
+    uint16_t hitTestFlags = nsSVGUtils::GetGeometryHitTestFlags(run.mFrame);
+
+    if ((hitTestFlags & SVG_HIT_TEST_FILL) ||
+        run.mFrame->GetStyleSVG()->mFill.mType != eStyleSVGPaintType_None) {
+      runFlags |= TextRenderedRun::eIncludeFill;
+    }
+    if ((hitTestFlags & SVG_HIT_TEST_STROKE) ||
+        nsSVGUtils::HasStroke(run.mFrame)) {
+      runFlags |= TextRenderedRun::eIncludeStroke;
+    }
+
+    if (runFlags) {
+      r = r.Union(run.GetUserSpaceRect(presContext, runFlags));
+    }
+  }
+  mRect =
+    nsLayoutUtils::RoundGfxRectToAppRect(r, presContext->AppUnitsPerCSSPixel());
+
+
+  if (mState & NS_FRAME_FIRST_REFLOW) {
+    // Make sure we have our filter property (if any) before calling
+    // FinishAndStoreOverflow (subsequent filter changes are handled off
+    // nsChangeHint_UpdateEffects):
+    nsSVGEffects::UpdateEffects(this);
+  }
+
+  // We only invalidate if we are dirty, if our outer-<svg> has already had its
+  // initial reflow (since if it hasn't, its entire area will be invalidated
+  // when it gets that initial reflow), and if our parent is not dirty (since
+  // if it is, then it will invalidate its entire new area, which will include
+  // our new area).
+  bool invalidate = (mState & NS_FRAME_IS_DIRTY) &&
+    !(GetParent()->GetStateBits() &
+       (NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY));
+
+  nsRect overflow = nsRect(nsPoint(0,0), mRect.Size());
+  nsOverflowAreas overflowAreas(overflow, overflow);
+  FinishAndStoreOverflow(overflowAreas, mRect.Size());
+
+  // Now unset the various reflow bits:
+  mState &= ~(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY |
+              NS_FRAME_HAS_DIRTY_CHILDREN);
+
+  // XXX nsSVGContainerFrame::ReflowSVG only looks at its nsISVGChildFrame
+  // children, and calls ConsiderChildOverflow on them.  Does it matter
+  // that ConsiderChildOverflow won't be called on our children?
+  nsSVGTextFrame2Base::ReflowSVG();
+
+  if (invalidate) {
+    // XXXSDL Let FinishAndStoreOverflow do this.
+    nsSVGUtils::InvalidateBounds(this, true);
+  }
+}
+
+/**
+ * Converts nsSVGUtils::eBBox* flags into TextRenderedRun flags appropriate
+ * for the specified rendered run.
+ */
+static uint32_t
+TextRenderedRunFlagsForBBoxContribution(const TextRenderedRun& aRun,
+                                        uint32_t aBBoxFlags)
+{
+  uint32_t flags = 0;
+  if ((aBBoxFlags & nsSVGUtils::eBBoxIncludeFillGeometry) ||
+      ((aBBoxFlags & nsSVGUtils::eBBoxIncludeFill) &&
+       aRun.mFrame->GetStyleSVG()->mFill.mType != eStyleSVGPaintType_None)) {
+    flags |= TextRenderedRun::eIncludeFill;
+  }
+  if ((aBBoxFlags & nsSVGUtils::eBBoxIncludeStrokeGeometry) ||
+      ((aBBoxFlags & nsSVGUtils::eBBoxIncludeStroke) &&
+       nsSVGUtils::HasStroke(aRun.mFrame))) {
+    flags |= TextRenderedRun::eIncludeStroke;
+  }
+  return flags;
+}
+
+SVGBBox
+nsSVGTextFrame2::GetBBoxContribution(const gfxMatrix &aToBBoxUserspace,
+                                     uint32_t aFlags)
+{
+  NS_ASSERTION(GetFirstPrincipalChild(), "must have a child frame");
+
+  UpdateGlyphPositioning(true);
+
+  gfxRect bbox;
+  nsPresContext* presContext = PresContext();
+
+  TextRenderedRunIterator it(this);
+  for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) {
+    uint32_t flags = TextRenderedRunFlagsForBBoxContribution(run, aFlags);
+    gfxRect bboxForRun =
+      run.GetUserSpaceRect(presContext, flags, &aToBBoxUserspace);
+    bbox = bbox.Union(bboxForRun);
+  }
+
+  return bbox;
+}
+
+//----------------------------------------------------------------------
+// nsSVGContainerFrame methods
+
+gfxMatrix
+nsSVGTextFrame2::GetCanvasTM(uint32_t aFor)
+{
+  if (!(GetStateBits() & NS_STATE_SVG_NONDISPLAY_CHILD)) {
+    if ((aFor == FOR_PAINTING && NS_SVGDisplayListPaintingEnabled()) ||
+        (aFor == FOR_HIT_TESTING && NS_SVGDisplayListHitTestingEnabled())) {
+      return nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(this);
+    }
+  }
+  if (!mCanvasTM) {
+    NS_ASSERTION(mParent, "null parent");
+
+    nsSVGContainerFrame *parent = static_cast<nsSVGContainerFrame*>(mParent);
+    dom::SVGTextContentElement *content = static_cast<dom::SVGTextContentElement*>(mContent);
+
+    gfxMatrix tm = content->PrependLocalTransformsTo(parent->GetCanvasTM(aFor));
+
+    mCanvasTM = new gfxMatrix(tm);
+  }
+  return *mCanvasTM;
+}
+
+//----------------------------------------------------------------------
+// nsSVGTextFrame2 SVG DOM methods
+
+/**
+ * Returns whether the specified node has any non-empty nsTextNodes
+ * beneath it.
+ */
+static bool
+HasTextContent(nsIContent* aContent)
+{
+  NS_ASSERTION(aContent, "expected non-null aContent");
+
+  TextNodeIterator it(aContent);
+  for (nsTextNode* text = it.Current(); text; text = it.Next()) {
+    if (text->TextLength() != 0) {
+      return true;
+    }
+  }
+  return false;
+}
+
+/**
+ * Returns the number of DOM characters beneath the specified node.
+ */
+static uint32_t
+GetTextContentLength(nsIContent* aContent)
+{
+  NS_ASSERTION(aContent, "expected non-null aContent");
+
+  uint32_t length = 0;
+  TextNodeIterator it(aContent);
+  for (nsTextNode* text = it.Current(); text; text = it.Next()) {
+    length += text->TextLength();
+  }
+  return length;
+}
+
+/**
+ * Returns the number of DOM characters beneath a node but which occur
+ * before a second node.
+ *
+ * @param aContent The node under which to look for nsTextNodes.
+ * @param aBefore The node before which, in document order, the candidate
+ *   nsTextNodes are to be found.
+ */
+static uint32_t
+GetTextContentLengthBefore(nsIContent* aContent, nsIContent* aBefore)
+{
+  NS_ASSERTION(aContent, "expected non-null aContent");
+  NS_ASSERTION(aBefore, "expected non-null aBefore");
+
+  uint32_t length = 0;
+  TextNodeIterator it(aContent, aBefore);
+  for (nsTextNode* text = it.Current();
+       text && !it.IsWithinSubtree() && !it.IsAfterSubtree();
+       text = it.Next()) {
+    length += text->TextLength();
+  }
+  return length;
+}
+
+/**
+ * Implements the SVG DOM GetNumberOfChars method for the specified
+ * text content element.
+ */
+uint32_t
+nsSVGTextFrame2::GetNumberOfChars(nsIContent* aContent)
+{
+  return GetTextContentLength(aContent);
+}
+
+/**
+ * Implements the SVG DOM GetComputedTextLength method for the specified
+ * text child element.
+ */
+float
+nsSVGTextFrame2::GetComputedTextLength(nsIContent* aContent)
+{
+  UpdateGlyphPositioning(false);
+
+  nsIFrame* kid = GetFirstPrincipalChild();
+  if (!kid) {
+    return 0.0f;
+  }
+
+  float cssPxPerDevPx = PresContext()->
+    AppUnitsToFloatCSSPixels(PresContext()->AppUnitsPerDevPixel());
+
+  nscoord length = 0;
+  TextRenderedRunIterator it(this, TextRenderedRunIterator::eAllFrames,
+                             aContent);
+  for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) {
+    length += run.GetAdvanceWidth();
+  }
+
+  return PresContext()->AppUnitsToGfxUnits(length) *
+           cssPxPerDevPx / mFontSizeScaleFactor;
+}
+
+/**
+ * Implements the SVG DOM GetSubStringLength method for the specified
+ * text content element.
+ */
+float
+nsSVGTextFrame2::GetSubStringLength(nsIContent* aContent,
+                                    uint32_t charnum, uint32_t nchars)
+{
+  UpdateGlyphPositioning(false);
+
+  nsIFrame* kid = GetFirstPrincipalChild();
+  if (!kid) {
+    return 0.0f;
+  }
+
+  // Convert charnum to be an offset into the whole <text> element, not just
+  // into aContent which might be a child <tspan>, etc.
+  charnum += GetTextContentLengthBefore(mContent, aContent);
+
+  nscoord textLength = 0;
+  TextRenderedRunIterator it(this);
+  TextRenderedRun run = it.Current();
+  while (run.mFrame) {
+    // If this rendered run is past the substring we are interested in, we
+    // are done.
+    uint32_t offset = run.mTextElementCharIndex;
+    if (offset >= charnum + nchars) {
+      break;
+    }
+
+    // Intersect the substring we are interested in with the range covered by
+    // the rendered run.
+    uint32_t length = run.mTextFrameContentLength;
+    IntersectInterval(offset, length, charnum, nchars);
+
+    if (length != 0) {
+      // Convert offset into an index into the run.
+      offset -= run.mTextElementCharIndex;
+
+      gfxSkipCharsIterator it =
+        run.mFrame->EnsureTextRun(nsTextFrame::eInflated);
+      gfxTextRun* textRun = run.mFrame->GetTextRun(nsTextFrame::eInflated);
+      ConvertOriginalToSkipped(it, offset, length);
+
+      // Accumulate the advance.
+      textLength += textRun->GetAdvanceWidth(offset, length, nullptr);
+    }
+
+    run = it.Next();
+  }
+
+  nsPresContext* presContext = PresContext();
+  float cssPxPerDevPx = presContext->
+    AppUnitsToFloatCSSPixels(presContext->AppUnitsPerDevPixel());
+
+  return presContext->AppUnitsToGfxUnits(textLength) *
+           cssPxPerDevPx / mFontSizeScaleFactor;
+}
+
+/**
+ * Implements the SVG DOM GetCharNumAtPosition method for the specified
+ * text content element.
+ */
+int32_t
+nsSVGTextFrame2::GetCharNumAtPosition(nsIContent* aContent,
+                                      mozilla::nsISVGPoint* aPoint)
+{
+  UpdateGlyphPositioning(false);
+
+  nsIFrame* kid = GetFirstPrincipalChild();
+  if (!kid) {
+    return 0.0f;
+  }
+
+  nsPresContext* context = PresContext();
+
+  gfxPoint p(aPoint->X(), aPoint->Y());
+
+  int32_t result = -1;
+
+  TextRenderedRunIterator it(this, TextRenderedRunIterator::eAllFrames, aContent);
+  for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) {
+    // Hit test this rendered run.  Later runs will override earlier ones.
+    int32_t index = run.GetCharNumAtPosition(context, p);
+    if (index != -1) {
+      result = index + run.mTextElementCharIndex;
+    }
+  }
+
+  if (result == -1) {
+    return result;
+  }
+
+  // Transform the result index into an index relative to aContent.
+  return result - GetTextContentLengthBefore(mContent, aContent);
+}
+
+/**
+ * Implements the SVG DOM GetStartPositionOfChar method for the specified
+ * text content element.
+ */
+nsresult
+nsSVGTextFrame2::GetStartPositionOfChar(nsIContent* aContent,
+                                        uint32_t aCharNum,
+                                        mozilla::nsISVGPoint** aResult)
+{
+  UpdateGlyphPositioning(false);
+
+  uint32_t textBefore = GetTextContentLengthBefore(mContent, aContent);
+  uint32_t textWithin = GetTextContentLength(aContent);
+
+  if (aCharNum >= textWithin) {
+    return NS_ERROR_DOM_INDEX_SIZE_ERR;
+  }
+
+  CharIterator it(this, CharIterator::eNonSkipped);
+  if (!it.AdvanceToCharacter(aCharNum + textBefore)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  // We need to return the start position of the whole glyph.
+  uint32_t startIndex = it.GlyphStartTextElementCharIndex();
+
+  NS_ADDREF(*aResult = new DOMSVGPoint(mPositions[startIndex].mPosition));
+  return NS_OK;
+}
+
+/**
+ * Implements the SVG DOM GetEndPositionOfChar method for the specified
+ * text content element.
+ */
+nsresult
+nsSVGTextFrame2::GetEndPositionOfChar(nsIContent* aContent,
+                                      uint32_t aCharNum,
+                                      mozilla::nsISVGPoint** aResult)
+{
+  UpdateGlyphPositioning(false);
+
+  uint32_t textBefore = GetTextContentLengthBefore(mContent, aContent);
+  uint32_t textWithin = GetTextContentLength(aContent);
+
+  if (aCharNum >= textWithin) {
+    return NS_ERROR_DOM_INDEX_SIZE_ERR;
+  }
+
+  CharIterator it(this, CharIterator::eNonSkipped);
+  if (!it.AdvanceToCharacter(aCharNum + textBefore)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  // We need to return the end position of the whole glyph.
+  uint32_t startIndex = it.GlyphStartTextElementCharIndex();
+
+  // Get the advance of the glyph.
+  gfxFloat advance = it.GetGlyphAdvance(PresContext());
+  if (it.TextRun()->IsRightToLeft()) {
+    advance = -advance;
+  }
+
+  // The end position is the start position plus the advance in the direction
+  // of the glyph's rotation.
+  gfxMatrix m;
+  m.Translate(mPositions[startIndex].mPosition);
+  m.Rotate(mPositions[startIndex].mAngle);
+  gfxPoint p = m.Transform(gfxPoint(advance / mFontSizeScaleFactor, 0));
+
+  NS_ADDREF(*aResult = new DOMSVGPoint(p));
+  return NS_OK;
+}
+
+/**
+ * Implements the SVG DOM GetExtentOfChar method for the specified
+ * text content element.
+ */
+nsresult
+nsSVGTextFrame2::GetExtentOfChar(nsIContent* aContent,
+                                 uint32_t aCharNum,
+                                 nsIDOMSVGRect** aResult)
+{
+  UpdateGlyphPositioning(false);
+
+  uint32_t textBefore = GetTextContentLengthBefore(mContent, aContent);
+  uint32_t textWithin = GetTextContentLength(aContent);
+
+  if (aCharNum >= textWithin) {
+    return NS_ERROR_DOM_INDEX_SIZE_ERR;
+  }
+
+  CharIterator it(this, CharIterator::eNonSkipped);
+  if (!it.AdvanceToCharacter(aCharNum + textBefore)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsPresContext* presContext = PresContext();
+
+  float cssPxPerDevPx = presContext->
+    AppUnitsToFloatCSSPixels(presContext->AppUnitsPerDevPixel());
+
+  // We need to return the extent of the whole glyph.
+  uint32_t startIndex = it.GlyphStartTextElementCharIndex();
+
+  // The ascent and descent gives the height of the glyph.
+  gfxFloat ascent, descent;
+  GetAscentAndDescentInAppUnits(it.TextFrame(), ascent, descent);
+
+  // Get the advance of the glyph.
+  gfxFloat advance = it.GetGlyphAdvance(presContext);
+  gfxFloat x = it.TextRun()->IsRightToLeft() ? -advance : 0.0;
+
+  // The horizontal extent is the origin of the glyph plus the advance
+  // in the direction of the glyph's rotation.
+  gfxMatrix m;
+  m.Translate(mPositions[startIndex].mPosition);
+  m.Rotate(mPositions[startIndex].mAngle);
+  m.Scale(1 / mFontSizeScaleFactor, 1 / mFontSizeScaleFactor);
+
+  gfxRect glyphRect
+    (x, -presContext->AppUnitsToGfxUnits(ascent) * cssPxPerDevPx,
+     advance, presContext->AppUnitsToGfxUnits(ascent + descent) * cssPxPerDevPx);
+
+  // Transform the glyph's rect into user space.
+  gfxRect r = m.TransformBounds(glyphRect);
+
+  NS_ADDREF(*aResult = new nsSVGRect(r.x, r.y, r.width, r.height));
+  return NS_OK;
+}
+
+/**
+ * Implements the SVG DOM GetRotationOfChar method for the specified
+ * text content element.
+ */
+nsresult
+nsSVGTextFrame2::GetRotationOfChar(nsIContent* aContent,
+                                   uint32_t aCharNum,
+                                   float* aResult)
+{
+  UpdateGlyphPositioning(false);
+
+  uint32_t textBefore = GetTextContentLengthBefore(mContent, aContent);
+  uint32_t textWithin = GetTextContentLength(aContent);
+  if (aCharNum >= textWithin) {
+    return NS_ERROR_DOM_INDEX_SIZE_ERR;
+  }
+
+  *aResult = mPositions[aCharNum + textBefore].mAngle * 180.0 / M_PI;
+  return NS_OK;
+}
+
+//----------------------------------------------------------------------
+// nsSVGTextFrame2 text layout methods
+
+/**
+ * Given the character position array before values have been filled in
+ * to any unspecified positions, and an array of dx/dy values, returns whether
+ * a character at a given index should start a new rendered run.
+ *
+ * @param aPositions The array of character positions before unspecified
+ *   positions have been filled in and dx/dy values have been added to them.
+ * @param aDeltas The array of dx/dy values.
+ * @param aIndex The character index in question.
+ */
+static bool
+ShouldStartRunAtIndex(const nsTArray<CharPosition>& aPositions,
+                      const nsTArray<gfxPoint>& aDeltas,
+                      uint32_t aIndex)
+{
+  if (aIndex == 0) {
+    return true;
+  }
+
+  if (aIndex < aPositions.Length()) {
+    // If an explicit x or y value was given, start a new run.
+    if (aPositions[aIndex].IsXSpecified() ||
+        aPositions[aIndex].IsYSpecified()) {
+      return true;
+    }
+
+    // If a non-zero rotation was given, or the previous character had a non-
+    // zero rotation, start a new run.
+    if ((aPositions[aIndex].IsAngleSpecified() &&
+         aPositions[aIndex].mAngle != 0.0f) ||
+        (aPositions[aIndex - 1].IsAngleSpecified() &&
+         (aPositions[aIndex - 1].mAngle != 0.0f))) {
+      return true;
+    }
+  }
+
+  if (aIndex < aDeltas.Length()) {
+    // If a non-zero dx or dy value was given, start a new run.
+    if (aDeltas[aIndex].x != 0.0 ||
+        aDeltas[aIndex].y != 0.0) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
+uint32_t
+nsSVGTextFrame2::ResolvePositions(nsIContent* aContent,
+                                  uint32_t aIndex,
+                                  bool aInTextPath,
+                                  bool& aForceStartOfChunk,
+                                  nsTArray<gfxPoint>& aDeltas)
+{
+  if (aContent->IsNodeOfType(nsINode::eTEXT)) {
+    // We found a text node.
+    uint32_t length = static_cast<nsTextNode*>(aContent)->TextLength();
+    if (length) {
+      if (aForceStartOfChunk) {
+        // Note this character as starting a new anchored chunk.
+        mPositions[aIndex].mStartOfChunk = true;
+        aForceStartOfChunk = false;
+      }
+      uint32_t end = aIndex + length;
+      while (aIndex < end) {
+        // Record whether each of these characters should start a new rendered
+        // run.  That is always the case for characters on a text path.
+        //
+        // Run boundaries due to rotate="" values are handled in
+        // DoGlyphPositioning.
+        if (aInTextPath || ShouldStartRunAtIndex(mPositions, aDeltas, aIndex)) {
+          mPositions[aIndex].mRunBoundary = true;
+        }
+        aIndex++;
+      }
+    }
+    return aIndex;
+  }
+
+  // Skip past elements that aren't text content elements.
+  if (!IsTextContentElement(aContent)) {
+    return aIndex;
+  }
+
+  if (aContent->Tag() == nsGkAtoms::textPath) {
+    // <textPath> elements are as if they are specified with x="0" y="0", but
+    // only if they actually have some text content.
+    if (HasTextContent(aContent)) {
+      mPositions[aIndex].mPosition = gfxPoint();
+    }
+  } else if (aContent->Tag() != nsGkAtoms::a) {
+    // We have a text content element that can have x/y/dx/dy/rotate attributes.
+    nsSVGElement* element = static_cast<nsSVGElement*>(aContent);
+
+    // Get x, y, dx, dy.
+    SVGUserUnitList x, y, dx, dy;
+    element->GetAnimatedLengthListValues(&x, &y, &dx, &dy);
+
+    // Get rotate.
+    const SVGNumberList* rotate = nullptr;
+    SVGAnimatedNumberList* animatedRotate =
+      element->GetAnimatedNumberList(nsGkAtoms::rotate);
+    if (animatedRotate) {
+      rotate = &animatedRotate->GetAnimValue();
+    }
+
+    uint32_t count = GetTextContentLength(aContent);
+
+    // New text anchoring chunks start at each character assigned a position
+    // with x="" or y="", or if we forced one with aForceStartOfChunk due to
+    // being just after a <textPath>.
+    uint32_t newChunkCount = std::max(x.Length(), y.Length());
+    if (!newChunkCount && aForceStartOfChunk) {
+      newChunkCount = 1;
+    }
+    for (uint32_t i = 0, j = 0; i < newChunkCount && j < count; j++) {
+      if (!mPositions[aIndex + j].mUnaddressable) {
+        mPositions[aIndex + j].mStartOfChunk = true;
+        i++;
+      }
+    }
+
+    // Copy dx="" and dy="" values into aDeltas.
+    if (!dx.IsEmpty() || !dy.IsEmpty()) {
+      // Any unspecified deltas when we grow the array just get left as 0s.
+      aDeltas.EnsureLengthAtLeast(aIndex + count);
+      for (uint32_t i = 0, j = 0; i < dx.Length() && j < count; j++) {
+        if (!mPositions[aIndex + j].mUnaddressable) {
+          aDeltas[aIndex + j].x = dx[i];
+          i++;
+        }
+      }
+      for (uint32_t i = 0, j = 0; i < dy.Length() && j < count; j++) {
+        if (!mPositions[aIndex + j].mUnaddressable) {
+          aDeltas[aIndex + j].y = dy[i];
+          i++;
+        }
+      }
+    }
+
+    // Copy x="" and y="" values.
+    for (uint32_t i = 0, j = 0; i < x.Length() && j < count; j++) {
+      if (!mPositions[aIndex + j].mUnaddressable) {
+        mPositions[aIndex + j].mPosition.x = x[i];
+        i++;
+      }
+    }
+    for (uint32_t i = 0, j = 0; i < y.Length() && j < count; j++) {
+      if (!mPositions[aIndex + j].mUnaddressable) {
+        mPositions[aIndex + j].mPosition.y = y[i];
+        i++;
+      }
+    }
+
+    // Copy rotate="" values.
+    if (rotate && !rotate->IsEmpty()) {
+      uint32_t i = 0, j = 0;
+      while (i < rotate->Length() && j < count) {
+        if (!mPositions[aIndex + j].mUnaddressable) {
+          mPositions[aIndex + j].mAngle = M_PI * (*rotate)[i] / 180.0;
+          i++;
+        }
+        j++;
+      }
+      // Propagate final rotate="" value to the end of this element.
+      while (j < count) {
+        if (!mPositions[aIndex + j].IsAngleSpecified()) {
+          mPositions[aIndex + j].mAngle = mPositions[aIndex + j - 1].mAngle;
+        }
+        j++;
+      }
+    }
+  }
+
+  // Recurse to children.
+  bool inTextPath = aInTextPath || aContent->Tag() == nsGkAtoms::textPath;
+  for (nsIContent* child = aContent->GetFirstChild();
+       child;
+       child = child->GetNextSibling()) {
+    aIndex = ResolvePositions(child, aIndex, inTextPath, aForceStartOfChunk,
+                              aDeltas);
+  }
+
+  if (aContent->Tag() == nsGkAtoms::textPath) {
+    // Force a new anchored chunk just after a <textPath>.
+    aForceStartOfChunk = true;
+  }
+
+  return aIndex;
+}
+
+bool
+nsSVGTextFrame2::ResolvePositions(nsTArray<gfxPoint>& aDeltas)
+{
+  NS_ASSERTION(mPositions.IsEmpty(), "expected mPositions to be empty");
+
+  CharIterator it(this, CharIterator::eOriginal);
+  if (it.AtEnd()) {
+    return false;
+  }
+
+  // We assume the first character position is (0,0) unless we later see
+  // otherwise, and note it as unaddressable if it is.
+  bool firstCharUnaddressable = it.IsOriginalCharUnaddressable();
+  mPositions.AppendElement(CharPosition::Unspecified(firstCharUnaddressable));
+
+  // Fill in unspecified positions for all remaining characters, noting
+  // them as unaddressable if they are.
+  uint32_t index = it.TextElementCharIndex();
+  for (uint32_t i = 0; i < index; i++) {
+    mPositions.AppendElement(CharPosition::Unspecified(false));
+  }
+  while (it.Next()) {
+    while (++index < it.TextElementCharIndex()) {
+      mPositions.AppendElement(CharPosition::Unspecified(false));
+    }
+    mPositions.AppendElement(CharPosition::Unspecified(
+                                             it.IsOriginalCharUnaddressable()));
+  }
+
+  // Recurse over the content and fill in character positions as we go.
+  bool forceStartOfChunk = false;
+  return ResolvePositions(mContent, 0, false, forceStartOfChunk, aDeltas) != 0;
+}
+
+void
+nsSVGTextFrame2::DetermineCharPositions(nsTArray<nsPoint>& aPositions)
+{
+  NS_ASSERTION(aPositions.IsEmpty(), "expected aPositions to be empty");
+
+  nsPoint position, lastPosition;
+
+  float cssPxPerDevPx = PresContext()->
+    AppUnitsToFloatCSSPixels(PresContext()->AppUnitsPerDevPixel());
+
+  TextFrameIterator frit(this);
+  for (nsTextFrame* frame = frit.Current(); frame; frame = frit.Next()) {
+    gfxSkipCharsIterator it = frame->EnsureTextRun(nsTextFrame::eInflated);
+    gfxTextRun* textRun = frame->GetTextRun(nsTextFrame::eInflated);
+
+    // Reset the position to the new frame's position.
+    position = frit.Position();
+    if (textRun->IsRightToLeft()) {
+      position.x += frame->GetRect().width;
+    }
+    position.y += GetBaselinePosition(frame, textRun, frit.DominantBaseline());
+    position = nsPoint(nscoord(position.x * cssPxPerDevPx),
+                       nscoord(position.y * cssPxPerDevPx));
+
+    // Any characters not in a frame, e.g. when display:none.
+    for (uint32_t i = 0; i < frit.UndisplayedCharacters(); i++) {
+      aPositions.AppendElement(position);
+    }
+
+    // Any white space characters trimmed at the start of the line of text.
+    nsTextFrame::TrimmedOffsets trimmedOffsets =
+      frame->GetTrimmedOffsets(frame->GetContent()->GetText(), true);
+    while (it.GetOriginalOffset() < trimmedOffsets.mStart) {
+      aPositions.AppendElement(position);
+      it.AdvanceOriginal(1);
+    }
+
+    // If a ligature was started in the previous frame, we should record
+    // the ligature's start position, not any partial position.
+    while (it.GetOriginalOffset() < frame->GetContentEnd() &&
+           !it.IsOriginalCharSkipped() &&
+           (!textRun->IsLigatureGroupStart(it.GetSkippedOffset()) ||
+            !textRun->IsClusterStart(it.GetSkippedOffset()))) {
+      gfxFloat advance =
+        textRun->GetAdvanceWidth(it.GetSkippedOffset(), 1,
+                                 nullptr) * cssPxPerDevPx;
+      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);
+        gfxFloat advance =
+          textRun->GetAdvanceWidth(it.GetSkippedOffset(), length,
+                                   nullptr) * cssPxPerDevPx;
+        position.x += textRun->IsRightToLeft() ? -advance : advance;
+        lastPosition = position;
+      }
+      it.AdvanceOriginal(1);
+    }
+  }
+
+  // Finally any characters at the end that are not in a frame.
+  for (uint32_t i = 0; i < frit.UndisplayedCharacters(); i++) {
+    aPositions.AppendElement(position);
+  }
+}
+
+/**
+ * Physical text-anchor values.
+ */
+enum TextAnchorSide {
+  eAnchorLeft,
+  eAnchorMiddle,
+  eAnchorRight
+};
+
+/**
+ * Converts a logical text-anchor value to its physical value, based on whether
+ * it is for an RTL frame.
+ */
+static TextAnchorSide
+ConvertLogicalTextAnchorToPhysical(uint8_t aTextAnchor, bool aIsRightToLeft)
+{
+  NS_ASSERTION(aTextAnchor <= 3, "unexpected value for aTextAnchor");
+  if (!aIsRightToLeft)
+    return TextAnchorSide(aTextAnchor);
+  return TextAnchorSide(2 - aTextAnchor);
+}
+
+/**
+ * Shifts the recorded character positions for an anchored chunk.
+ *
+ * @param aCharPositions The recorded character positions.
+ * @param aChunkStart The character index the starts the anchored chunk.  This
+ *   character's initial position is the anchor point.
+ * @param aChunkEnd The character index just after the end of the anchored
+ *   chunk.
+ * @param aLeftEdge The left-most edge of any of the glyphs within the
+ *   anchored chunk.
+ * @param aRightEdge The right-most edge of any of the glyphs within the
+ *   anchored chunk.
+ * @param aAnchorSide The direction to anchor.
+ */
+static void
+ShiftAnchoredChunk(nsTArray<mozilla::CharPosition>& aCharPositions,
+                   uint32_t aChunkStart,
+                   uint32_t aChunkEnd,
+                   gfxFloat aLeftEdge,
+                   gfxFloat aRightEdge,
+                   TextAnchorSide aAnchorSide)
+{
+  NS_ASSERTION(aLeftEdge <= aRightEdge, "unexpected anchored chunk edges");
+  NS_ASSERTION(aChunkStart < aChunkEnd, "unexpected values for aChunkStart and "
+                                        "aChunkEnd");
+
+  gfxFloat shift = aCharPositions[aChunkStart].mPosition.x;
+  switch (aAnchorSide) {
+    case eAnchorLeft:
+      shift -= aLeftEdge;
+      break;
+    case eAnchorMiddle:
+      shift -= (aLeftEdge + aRightEdge) / 2;
+      break;
+    case eAnchorRight:
+      shift -= aRightEdge;
+      break;
+    default:
+      NS_NOTREACHED("unexpected value for aAnchorSide");
+  }
+
+  if (shift != 0.0) {
+    for (uint32_t i = aChunkStart; i < aChunkEnd; i++) {
+      aCharPositions[i].mPosition.x += shift;
+    }
+  }
+}
+
+void
+nsSVGTextFrame2::AdjustChunksForLineBreaks()
+{
+  nsBlockFrame* block = nsLayoutUtils::GetAsBlock(GetFirstPrincipalChild());
+  NS_ASSERTION(block, "expected block frame");
+
+  nsBlockFrame::line_iterator line = block->begin_lines();
+
+  CharIterator it(this, CharIterator::eOriginal);
+  while (!it.AtEnd() && line != block->end_lines()) {
+    if (it.TextFrame() == line->mFirstChild) {
+      mPositions[it.TextElementCharIndex()].mStartOfChunk = true;
+      line++;
+    }
+    it.AdvancePastCurrentFrame();
+  }
+}
+
+void
+nsSVGTextFrame2::AdjustPositionsForClusters()
+{
+  nsPresContext* presContext = PresContext();
+
+  CharIterator it(this, CharIterator::eClusterOrLigatureGroupMiddle);
+  while (!it.AtEnd()) {
+    // Find the start of the cluster/ligature group.
+    uint32_t charIndex = it.TextElementCharIndex();
+    uint32_t startIndex = it.GlyphStartTextElementCharIndex();
+
+    mPositions[charIndex].mClusterOrLigatureGroupMiddle = true;
+
+    // Don't allow different rotations on ligature parts.
+    bool rotationAdjusted = false;
+    double angle = mPositions[startIndex].mAngle;
+    if (mPositions[charIndex].mAngle != angle) {
+      mPositions[charIndex].mAngle = angle;
+      rotationAdjusted = true;
+    }
+
+    // Find out the partial glyph advance for this character and update
+    // the character position.
+    gfxFloat advance =
+      it.GetGlyphPartialAdvance(0, charIndex - startIndex, presContext) /
+        mFontSizeScaleFactor;
+    gfxPoint direction = gfxPoint(cos(angle), sin(angle)) *
+                         (it.TextRun()->IsRightToLeft() ? -1.0 : 1.0);
+    mPositions[charIndex].mPosition = mPositions[startIndex].mPosition +
+                                      direction * advance;
+
+    // Ensure any runs that would end in the middle of a ligature now end just
+    // after the ligature.
+    if (mPositions[charIndex].mRunBoundary) {
+      mPositions[charIndex].mRunBoundary = false;
+      if (charIndex + 1 < mPositions.Length()) {
+        mPositions[charIndex + 1].mRunBoundary = true;
+      }
+    } else if (rotationAdjusted) {
+      if (charIndex + 1 < mPositions.Length()) {
+        mPositions[charIndex + 1].mRunBoundary = true;
+      }
+    }
+
+    // Ensure any anchored chunks that would begin in the middle of a ligature
+    // now begin just after the ligature.
+    if (mPositions[charIndex].mStartOfChunk) {
+      mPositions[charIndex].mStartOfChunk = false;
+      if (charIndex + 1 < mPositions.Length()) {
+        mPositions[charIndex + 1].mStartOfChunk = true;
+      }
+    }
+
+    it.Next();
+  }
+}
+
+nsIFrame*
+nsSVGTextFrame2::GetTextPathPathFrame(nsIFrame* aTextPathFrame)
+{
+  nsSVGTextPathProperty *property = static_cast<nsSVGTextPathProperty*>
+    (aTextPathFrame->Properties().Get(nsSVGEffects::HrefProperty()));
+
+  if (!property) {
+    nsIContent* content = aTextPathFrame->GetContent();
+    dom::SVGTextPathElement* tp = static_cast<dom::SVGTextPathElement*>(content);
+    nsAutoString href;
+    tp->mStringAttributes[dom::SVGTextPathElement::HREF].GetAnimValue(href, tp);
+    if (href.IsEmpty()) {
+      return nullptr; // no URL
+    }
+
+    nsCOMPtr<nsIURI> targetURI;
+    nsCOMPtr<nsIURI> base = content->GetBaseURI();
+    nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(targetURI), href,
+                                              content->GetCurrentDoc(), base);
+
+    property = nsSVGEffects::GetTextPathProperty(targetURI, aTextPathFrame,
+                                                 nsSVGEffects::HrefProperty());
+    if (!property)
+      return nullptr;
+  }
+
+  return property->GetReferencedFrame(nsGkAtoms::svgPathGeometryFrame, nullptr);
+}
+
+already_AddRefed<gfxFlattenedPath>
+nsSVGTextFrame2::GetFlattenedTextPath(nsIFrame* aTextPathFrame)
+{
+  nsIFrame *path = GetTextPathPathFrame(aTextPathFrame);
+
+  if (path) {
+    nsSVGPathGeometryElement *element =
+      static_cast<nsSVGPathGeometryElement*>(path->GetContent());
+
+    return element->GetFlattenedPath(element->PrependLocalTransformsTo(gfxMatrix()));
+  }
+  return nullptr;
+}
+
+gfxFloat
+nsSVGTextFrame2::GetOffsetScale(nsIFrame* aTextPathFrame)
+{
+  nsIFrame *pathFrame = GetTextPathPathFrame(aTextPathFrame);
+  if (!pathFrame)
+    return 1.0;
+
+  return static_cast<dom::SVGPathElement*>(pathFrame->GetContent())->
+    GetPathLengthScale(dom::SVGPathElement::eForTextPath);
+}
+
+gfxFloat
+nsSVGTextFrame2::GetStartOffset(nsIFrame* aTextPathFrame)
+{
+  dom::SVGTextPathElement *tp =
+    static_cast<dom::SVGTextPathElement*>(aTextPathFrame->GetContent());
+  nsSVGLength2 *length =
+    &tp->mLengthAttributes[dom::SVGTextPathElement::STARTOFFSET];
+
+  if (length->IsPercentage()) {
+    nsRefPtr<gfxFlattenedPath> data = GetFlattenedTextPath(aTextPathFrame);
+    return data ?
+             length->GetAnimValInSpecifiedUnits() * data->GetLength() / 100.0 :
+             0.0;
+  }
+  return length->GetAnimValue(tp) * GetOffsetScale(aTextPathFrame);
+}
+
+void
+nsSVGTextFrame2::DoTextPathLayout()
+{
+  nsPresContext* context = PresContext();
+
+  CharIterator it(this, CharIterator::eClusterAndLigatureGroupStart);
+  while (!it.AtEnd()) {
+    nsIFrame* textPathFrame = it.TextPathFrame();
+    if (!textPathFrame) {
+      // Skip past this frame if we're not in a text path.
+      it.AdvancePastCurrentFrame();
+      continue;
+    }
+
+    // Get the path itself.
+    nsRefPtr<gfxFlattenedPath> data = GetFlattenedTextPath(textPathFrame);
+    if (!data) {
+      it.AdvancePastCurrentTextPathFrame();
+      continue;
+    }
+
+    nsIContent* textPath = textPathFrame->GetContent();
+
+    gfxFloat offset = GetStartOffset(textPathFrame);
+    gfxFloat pathLength = data->GetLength();
+
+    // Loop for each text frame in the text path.
+    do {
+      uint32_t i = it.TextElementCharIndex();
+      gfxFloat halfAdvance =
+        it.GetGlyphAdvance(context) / mFontSizeScaleFactor / 2.0;
+      gfxFloat sign = it.TextRun()->IsRightToLeft() ? -1.0 : 1.0;
+      gfxFloat midx = mPositions[i].mPosition.x + sign * halfAdvance + offset;
+
+      // Hide the character if it falls off the end of the path.
+      mPositions[i].mHidden = midx < 0 || midx > pathLength;
+
+      // Position the character on the path at the right angle.
+      double angle;
+      gfxPoint pt =
+        data->FindPoint(gfxPoint(midx, mPositions[i].mPosition.y), &angle);
+      gfxPoint direction = gfxPoint(cos(angle), sin(angle)) * sign;
+      mPositions[i].mPosition = pt - direction * halfAdvance;
+      mPositions[i].mAngle += angle;
+
+      // Position any characters for a partial ligature.
+      for (uint32_t j = i + 1;
+           j < mPositions.Length() && mPositions[j].mClusterOrLigatureGroupMiddle;
+           j++) {
+        gfxPoint partialAdvance =
+          direction * it.GetGlyphPartialAdvance(0, j - i, context) /
+                                                         mFontSizeScaleFactor;
+        mPositions[j].mPosition = mPositions[i].mPosition + partialAdvance;
+        mPositions[j].mAngle = mPositions[i].mAngle;
+        mPositions[j].mHidden = mPositions[i].mHidden;
+      }
+      it.Next();
+    } while (it.TextPathFrame() &&
+             it.TextPathFrame()->GetContent() == textPath);
+  }
+}
+
+void
+nsSVGTextFrame2::DoAnchoring()
+{
+  nsPresContext* presContext = PresContext();
+
+  CharIterator it(this, CharIterator::eOriginal);
+
+  // Don't need to worry about skipped or trimmed characters.
+  while (!it.AtEnd() &&
+         (it.IsOriginalCharSkipped() || it.IsOriginalCharTrimmed())) {
+    it.Next();
+  }
+
+  uint32_t start = it.TextElementCharIndex();
+  while (start < mPositions.Length()) {
+    it.AdvanceToCharacter(start);
+    nsTextFrame* chunkFrame = it.TextFrame();
+
+    // Measure characters in this chunk to find the left-most and right-most
+    // edges of all glyphs within the chunk.
+    uint32_t index = it.TextElementCharIndex();
+    uint32_t end = start;
+    gfxFloat left = std::numeric_limits<gfxFloat>::infinity();
+    gfxFloat right = -std::numeric_limits<gfxFloat>::infinity();
+    do {
+      if (!it.IsOriginalCharSkipped() && !it.IsOriginalCharTrimmed()) {
+        gfxFloat advance = it.GetAdvance(presContext) / mFontSizeScaleFactor;
+        if (it.TextRun()->IsRightToLeft()) {
+          left  = std::min(left,  mPositions[index].mPosition.x - advance);
+          right = std::max(right, mPositions[index].mPosition.x);
+        } else {
+          left  = std::min(left,  mPositions[index].mPosition.x);
+          right = std::max(right, mPositions[index].mPosition.x + advance);
+        }
+      }
+      it.Next();
+      index = end = it.TextElementCharIndex();
+    } while (!it.AtEnd() && !mPositions[end].mStartOfChunk);
+
+    if (left != std::numeric_limits<gfxFloat>::infinity()) {
+      bool isRTL =
+        chunkFrame->GetStyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL;
+      TextAnchorSide anchor =
+        ConvertLogicalTextAnchorToPhysical(chunkFrame->GetStyleSVG()->mTextAnchor,
+                                           isRTL);
+
+      ShiftAnchoredChunk(mPositions, start, end, left, right, anchor);
+    }
+
+    start = it.TextElementCharIndex();
+  }
+}
+
+void
+nsSVGTextFrame2::DoGlyphPositioning()
+{
+  mPositions.Clear();
+  mPositioningDirty = false;
+
+  // Determine the positions of each character in app units.
+  nsTArray<nsPoint> charPositions;
+  DetermineCharPositions(charPositions);
+
+  if (charPositions.IsEmpty()) {
+    // No characters, so nothing to do.
+    return;
+  }
+
+  nsPresContext* presContext = PresContext();
+
+  // Get the x, y, dx, dy, rotate values for the subtree.
+  nsTArray<gfxPoint> deltas;
+  if (!ResolvePositions(deltas)) {
+    // We shouldn't reach here because DetermineCharPositions should have been
+    // empty if we fail to resolve any positions.
+    NS_NOTREACHED("unexpected result from ResolvePositions");
+    mPositions.Clear();
+    return;
+  }
+
+  // XXX We might be able to do less work when there is at most a single
+  // x/y/dx/dy position.
+
+  // Truncate the positioning arrays to the actual number of characters present.
+  TruncateTo(deltas, charPositions);
+  TruncateTo(mPositions, charPositions);
+
+  // Fill in an unspecified character position at index 0.
+  if (!mPositions[0].IsXSpecified()) {
+    mPositions[0].mPosition.x = 0.0;
+  }
+  if (!mPositions[0].IsYSpecified()) {
+    mPositions[0].mPosition.y = 0.0;
+  }
+  if (!mPositions[0].IsAngleSpecified()) {
+    mPositions[0].mAngle = 0.0;
+  }
+
+  // Fill in any unspecified character positions based on the positions recorded
+  // in charPositions, and also add in the dx/dy values.
+  if (!deltas.IsEmpty()) {
+    mPositions[0].mPosition += deltas[0];
+  }
+  for (uint32_t i = 1; i < mPositions.Length(); i++) {
+    // Fill in unspecified x position.
+    if (!mPositions[i].IsXSpecified()) {
+      nscoord d = charPositions[i].x - charPositions[i - 1].x;
+      mPositions[i].mPosition.x =
+        mPositions[i - 1].mPosition.x +
+        presContext->AppUnitsToGfxUnits(d) / mFontSizeScaleFactor;
+    }
+    // Fill in unspecified y position.
+    if (!mPositions[i].IsYSpecified()) {
+      nscoord d = charPositions[i].y - charPositions[i - 1].y;
+      mPositions[i].mPosition.y =
+        mPositions[i - 1].mPosition.y +
+        presContext->AppUnitsToGfxUnits(d) / mFontSizeScaleFactor;
+    }
+    // Add in dx/dy.
+    if (i < deltas.Length()) {
+      mPositions[i].mPosition += deltas[i];
+    }
+    // Fill in unspecified rotation values.
+    if (!mPositions[i].IsAngleSpecified()) {
+      mPositions[i].mAngle = mPositions[i - 1].mAngle;
+      if (mPositions[i].mAngle != 0.0f) {
+        // Any non-zero rotation must begin a run boundary.
+        mPositions[i].mRunBoundary = true;
+      }
+    }
+  }
+
+  // Fill in any remaining character positions after the specified
+  // x/y/rotate positions.
+  //
+  // XXX This may not be needed since ResolvePositions now initializes
+  // mPositions with an (unspecified) value for each character.
+  for (uint32_t i = mPositions.Length(); i < charPositions.Length(); i++) {
+    nscoord dx = charPositions[i].x - charPositions[i - 1].x;
+    nscoord dy = charPositions[i].y - charPositions[i - 1].y;
+
+    gfxPoint pt(mPositions[i - 1].mPosition.x +
+                  presContext->AppUnitsToGfxUnits(dx),
+                mPositions[i - 1].mPosition.y +
+                  presContext->AppUnitsToGfxUnits(dy));
+
+    mPositions.AppendElement(CharPosition(pt / mFontSizeScaleFactor,
+                                          mPositions[i - 1].mAngle));
+    if (i < deltas.Length()) {
+      mPositions[i].mPosition += deltas[i];
+    }
+  }
+
+  AdjustChunksForLineBreaks();
+  AdjustPositionsForClusters();
+  DoAnchoring();
+  DoTextPathLayout();
+}
+
+bool
+nsSVGTextFrame2::ShouldRenderAsPath(nsRenderingContext* aContext,
+                                    nsTextFrame* aFrame)
+{
+  // Rendering to a clip path.
+  if (SVGAutoRenderState::GetRenderMode(aContext) != SVGAutoRenderState::NORMAL) {
+    return true;
+  }
+
+  const nsStyleSVG* style = aFrame->GetStyleSVG();
+
+  // Fill is a non-solid paint, has a non-default fill-rule or has
+  // non-1 opacity.
+  if (!(style->mFill.mType == eStyleSVGPaintType_None ||
+        (style->mFill.mType == eStyleSVGPaintType_Color &&
+         style->mFillRule == NS_STYLE_FILL_RULE_NONZERO &&
+         style->mFillOpacity == 1))) {
+    return true;
+  }
+
+  // Text has a stroke.
+  if (!(style->mStroke.mType == eStyleSVGPaintType_None ||
+        style->mStrokeOpacity == 0 ||
+        nsSVGUtils::CoordToFloat(PresContext(),
+                                 static_cast<nsSVGElement*>(mContent),
+                                 style->mStrokeWidth) == 0)) {
+    return true;
+  }
+
+  return false;
+}
+
+void
+nsSVGTextFrame2::NotifyGlyphMetricsChange()
+{
+  mPositioningDirty = true;
+  nsSVGUtils::InvalidateBounds(this, false);
+  nsSVGUtils::ScheduleReflowSVG(this);
+}
+
+void
+nsSVGTextFrame2::UpdateGlyphPositioning(bool aForceGlobalTransform)
+{
+  nsIFrame* kid = GetFirstPrincipalChild();
+  if (!kid)
+    return;
+
+  bool needsReflow =
+    (mState & (NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN));
+
+  NS_ASSERTION(!(kid->GetStateBits() & NS_FRAME_IN_REFLOW),
+               "should not be in reflow when about to reflow again");
+
+  if (!needsReflow)
+    return;
+
+  if (mState & NS_FRAME_IS_DIRTY) {
+    // If we require a full reflow, ensure our kid is marked fully dirty.
+    kid->AddStateBits(NS_FRAME_IS_DIRTY);
+  }
+
+  if (needsReflow) {
+    nsPresContext::InterruptPreventer noInterrupts(PresContext());
+    DoReflow(aForceGlobalTransform);
+  }
+
+  DoGlyphPositioning();
+}
+
+void
+nsSVGTextFrame2::DoReflow(bool aForceGlobalTransform)
+{
+  mPositioningDirty = true;
+
+  nsPresContext *presContext = PresContext();
+  nsIFrame* kid = GetFirstPrincipalChild();
+  if (!kid)
+    return;
+
+  nsIPresShell* presShell = presContext->PresShell();
+  NS_ASSERTION(presShell, "null presShell");
+  nsRefPtr<nsRenderingContext> renderingContext =
+    presShell->GetReferenceRenderingContext();
+  if (!renderingContext)
+    return;
+
+  UpdateFontSizeScaleFactor(aForceGlobalTransform);
+
+  nscoord width = kid->GetPrefWidth(renderingContext);
+  nsHTMLReflowState reflowState(presContext, kid,
+                                renderingContext,
+                                nsSize(width, NS_UNCONSTRAINEDSIZE));
+  nsHTMLReflowMetrics desiredSize;
+  nsReflowStatus status;
+
+  NS_ASSERTION(reflowState.mComputedBorderPadding == nsMargin(0, 0, 0, 0) &&
+               reflowState.mComputedMargin == nsMargin(0, 0, 0, 0),
+               "style system should ensure that :-moz-svg-text "
+               "does not get styled");
+
+  kid->WillReflow(presContext);
+  kid->Reflow(presContext, desiredSize, reflowState, status);
+  kid->DidReflow(presContext, &reflowState, nsDidReflowStatus::FINISHED);
+  kid->SetSize(nsSize(desiredSize.width, desiredSize.height));
+
+  TextNodeCorrespondenceRecorder::RecordCorrespondence(this);
+}
+
+// Usable font size range in devpixels / user-units
+#define CLAMP_MIN_SIZE 8.0
+#define CLAMP_MAX_SIZE 200.0
+#define PRECISE_SIZE   200.0
+
+void
+nsSVGTextFrame2::UpdateFontSizeScaleFactor(bool aForceGlobalTransform)
+{
+  nsPresContext* presContext = PresContext();
+
+  bool geometricPrecision = false;
+  nscoord min = nscoord_MAX,
+          max = nscoord_MIN;
+
+  // Find the minimum and maximum font sizes used over all the
+  // nsTextFrames.
+  TextFrameIterator it(this);
+  nsTextFrame* f = it.Current();
+  while (f) {
+    if (!geometricPrecision) {
+      // Unfortunately we can't treat text-rendering:geometricPrecision
+      // separately for each text frame.
+      geometricPrecision = f->GetStyleSVG()->mTextRendering ==
+                             NS_STYLE_TEXT_RENDERING_GEOMETRICPRECISION;
+    }
+    nscoord size = f->GetStyleFont()->mFont.size;
+    if (size) {
+      min = std::min(min, size);
+      max = std::max(max, size);
+    }
+    f = it.Next();
+  }
+
+  if (min == nscoord_MAX) {
+    // No text, so no need for scaling.
+    mFontSizeScaleFactor = 1.0;
+    return;
+  }
+
+  gfxMatrix m;
+  if (aForceGlobalTransform ||
+      !(GetStateBits() & NS_STATE_SVG_NONDISPLAY_CHILD)) {
+    m = GetCanvasTM(mGetCanvasTMForFlag);
+    if (m.IsSingular()) {
+      mFontSizeScaleFactor = 1.0;
+      return;
+    }
+  }
+
+  float textZoom = presContext->TextZoom();
+  double minSize = presContext->AppUnitsToFloatCSSPixels(min) / textZoom;
+
+  if (geometricPrecision) {
+    // We want to ensure minSize is scaled to PRECISE_SIZE.
+    mFontSizeScaleFactor = PRECISE_SIZE / minSize;
+    return;
+  }
+
+  double maxSize = presContext->AppUnitsToFloatCSSPixels(max) / textZoom;
+
+  // The context scale is the ratio of the length of the transformed
+  // diagonal vector (1,1) to the length of the untransformed diagonal
+  // (which is sqrt(2)).
+  gfxPoint p = m.Transform(gfxPoint(1, 1)) - m.Transform(gfxPoint(0, 0));
+  double contextScale = SVGContentUtils::ComputeNormalizedHypotenuse(p.x, p.y);
+
+  double minTextRunSize = minSize * contextScale;
+  double maxTextRunSize = maxSize * contextScale;
+
+  if (minTextRunSize >= CLAMP_MIN_SIZE &&
+      maxTextRunSize <= CLAMP_MAX_SIZE) {
+    // We are already in the ideal font size range for all text frames,
+    // so we only have to take into account the contextScale.
+    mFontSizeScaleFactor = contextScale;
+    return;
+  }
+
+  if (maxSize / minSize > CLAMP_MAX_SIZE / CLAMP_MIN_SIZE) {
+    // We can't scale the font sizes so that all of the text frames lie
+    // within our ideal font size range, so we treat the minimum as more
+    // important and just scale so that minSize = CLAMP_MIN_SIZE.
+    mFontSizeScaleFactor = CLAMP_MIN_SIZE / minTextRunSize;
+    return;
+  }
+
+  if (minTextRunSize < CLAMP_MIN_SIZE) {
+    mFontSizeScaleFactor = CLAMP_MIN_SIZE / minTextRunSize;
+    return;
+  }
+
+  mFontSizeScaleFactor = CLAMP_MAX_SIZE / maxTextRunSize;
+}
+
+double
+nsSVGTextFrame2::GetFontSizeScaleFactor() const
+{
+  return mFontSizeScaleFactor;
+}
new file mode 100644
--- /dev/null
+++ b/layout/svg/nsSVGTextFrame2.h
@@ -0,0 +1,496 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NS_SVGTEXTFRAME2_H
+#define NS_SVGTEXTFRAME2_H
+
+#include "gfxMatrix.h"
+#include "gfxRect.h"
+#include "nsStubMutationObserver.h"
+#include "nsSVGTextContainerFrame.h"
+
+class nsDisplaySVGText;
+class nsRenderingContext;
+class nsTextFrame;
+
+typedef nsSVGDisplayContainerFrame nsSVGTextFrame2Base;
+
+namespace mozilla {
+
+class TextFrameIterator;
+class TextNodeCorrespondenceRecorder;
+struct TextRenderedRun;
+class TextRenderedRunIterator;
+
+/**
+ * Information about the positioning for a single character in an SVG <text>
+ * element.
+ *
+ * During SVG text layout, we use infinity values to represent positions and
+ * rotations that are not explicitly specified with x/y/rotate attributes.
+ */
+struct CharPosition
+{
+  CharPosition()
+    : mAngle(0),
+      mHidden(false),
+      mUnaddressable(false),
+      mClusterOrLigatureGroupMiddle(false),
+      mRunBoundary(false),
+      mStartOfChunk(false)
+  {
+  }
+
+  CharPosition(gfxPoint aPosition, double aAngle)
+    : mPosition(aPosition),
+      mAngle(aAngle),
+      mHidden(false),
+      mUnaddressable(false),
+      mClusterOrLigatureGroupMiddle(false),
+      mRunBoundary(false),
+      mStartOfChunk(false)
+  {
+  }
+
+  static CharPosition Unspecified(bool aUnaddressable)
+  {
+    CharPosition cp(UnspecifiedPoint(), UnspecifiedAngle());
+    cp.mUnaddressable = aUnaddressable;
+    return cp;
+  }
+
+  bool IsAngleSpecified() const
+  {
+    return mAngle != UnspecifiedAngle();
+  }
+
+  bool IsXSpecified() const
+  {
+    return mPosition.x != UnspecifiedCoord();
+  }
+
+  bool IsYSpecified() const
+  {
+    return mPosition.y != UnspecifiedCoord();
+  }
+
+  gfxPoint mPosition;
+  double mAngle;
+
+  // not displayed due to falling off the end of a <textPath>
+  bool mHidden;
+
+  // skipped in positioning attributes due to being collapsed-away white space
+  bool mUnaddressable;
+
+  // a preceding character is what positioning attributes address
+  bool mClusterOrLigatureGroupMiddle;
+
+  // rendering is split here since an explicit position or rotation was given
+  bool mRunBoundary;
+
+  // an anchored chunk begins here
+  bool mStartOfChunk;
+
+private:
+  static gfxFloat UnspecifiedCoord()
+  {
+    return std::numeric_limits<gfxFloat>::infinity();
+  }
+
+  static double UnspecifiedAngle()
+  {
+    return std::numeric_limits<double>::infinity();
+  }
+
+  static gfxPoint UnspecifiedPoint()
+  {
+    return gfxPoint(UnspecifiedCoord(), UnspecifiedCoord());
+  }
+};
+
+}
+
+/**
+ * Frame class for SVG <text> elements, used when the
+ * layout.svg.css-text.enabled is true.
+ *
+ * An nsSVGTextFrame2 manages SVG text layout, painting and interaction for
+ * all descendent text content elements.  The frame tree will look like this:
+ *
+ *   nsSVGTextFrame2                  -- for <text>
+ *     <anonymous block frame>
+ *       ns{Block,Inline,Text}Frames  -- for text nodes, <tspan>s, <a>s, etc.
+ *
+ * SVG text layout is done by:
+ *
+ *   1. Reflowing the anonymous block frame.
+ *   2. Inspecting the (app unit) positions of the glyph for each character in
+ *      the nsTextFrames underneath the anonymous block frame.
+ *   3. Determining the (user unit) positions for each character in the <text>
+ *      using the x/y/dx/dy/rotate attributes on all the text content elements,
+ *      and using the step 2 results to fill in any gaps.
+ *   4. Applying any other SVG specific text layout (anchoring and text paths)
+ *      to the positions computed in step 3.
+ *
+ * Rendering of the text is done by splitting up each nsTextFrame into ranges
+ * that can be contiguously painted.  (For example <text x="10 20">abcd</text>
+ * would have two contiguous ranges: one for the "a" and one for the "bcd".)
+ * Each range is called a "text rendered run", represented by a TextRenderedRun
+ * object.  The TextRenderedRunIterator class performs that splitting and
+ * returns a TextRenderedRun for each bit of text to be painted separately.
+ *
+ * Each rendered run is painted by calling nsTextFrame::PaintText.  If the text
+ * formatting is simple enough (solid fill, no stroking, etc.), PaintText will
+ * itself do the painting.  Otherwise, a DrawPathCallback is passed to
+ * PaintText so that we can fill the text geometry with SVG paint servers.
+ */
+class nsSVGTextFrame2 : public nsSVGTextFrame2Base
+{
+  friend nsIFrame*
+  NS_NewSVGTextFrame2(nsIPresShell* aPresShell, nsStyleContext* aContext);
+
+  friend class mozilla::TextFrameIterator;
+  friend class mozilla::TextNodeCorrespondenceRecorder;
+  friend struct mozilla::TextRenderedRun;
+  friend class mozilla::TextRenderedRunIterator;
+  friend class AutoCanvasTMForMarker;
+  friend class MutationObserver;
+  friend class nsDisplaySVGText;
+
+protected:
+  nsSVGTextFrame2(nsStyleContext* aContext)
+    : nsSVGTextFrame2Base(aContext),
+      mFontSizeScaleFactor(1.0f),
+      mGetCanvasTMForFlag(FOR_OUTERSVG_TM),
+      mPositioningDirty(true)
+  {
+  }
+
+public:
+  NS_DECL_QUERYFRAME_TARGET(nsSVGTextFrame2)
+  NS_DECL_QUERYFRAME
+  NS_DECL_FRAMEARENA_HELPERS
+
+  // nsIFrame:
+  NS_IMETHOD Init(nsIContent* aContent,
+                  nsIFrame*   aParent,
+                  nsIFrame*   aPrevInFlow);
+  NS_IMETHOD AttributeChanged(int32_t  aNameSpaceID,
+                              nsIAtom* aAttribute,
+                              int32_t  aModType);
+
+  NS_IMETHOD AttributeChanged(int32_t aNamespaceID,
+                              nsIAtom* aAttribute,
+                              int32_t aModType);
+
+  virtual nsIFrame* GetContentInsertionFrame()
+  {
+    return GetFirstPrincipalChild()->GetContentInsertionFrame();
+  }
+
+  NS_IMETHOD Reflow(nsPresContext*           aPresContext,
+                    nsHTMLReflowMetrics&     aDesiredSize,
+                    const nsHTMLReflowState& aReflowState,
+                    nsReflowStatus&          aStatus);
+
+  NS_IMETHOD BuildDisplayList(nsDisplayListBuilder*   aBuilder,
+                              const nsRect&           aDirtyRect,
+                              const nsDisplayListSet& aLists);
+
+  /**
+   * Get the "type" of the frame
+   *
+   * @see nsGkAtoms::svgTextFrame2
+   */
+  virtual nsIAtom* GetType() const;
+
+#ifdef DEBUG
+  NS_IMETHOD GetFrameName(nsAString& aResult) const
+  {
+    return MakeFrameName(NS_LITERAL_STRING("SVGText2"), aResult);
+  }
+#endif
+
+  virtual void InvalidateInternal(const nsRect& aDamageRect,
+                                  nscoord aX, nscoord aY, nsIFrame* aForChild,
+                                  uint32_t aFlags);
+
+  // nsISVGChildFrame interface:
+  virtual void NotifySVGChanged(uint32_t aFlags);
+  NS_IMETHOD PaintSVG(nsRenderingContext* aContext,
+                      const nsIntRect* aDirtyRect);
+  virtual void ReflowSVG();
+  NS_IMETHOD_(nsRect) GetCoveredRegion();
+  virtual SVGBBox GetBBoxContribution(const gfxMatrix& aToBBoxUserspace,
+                                      uint32_t aFlags);
+
+  // nsSVGContainerFrame methods:
+  virtual gfxMatrix GetCanvasTM(uint32_t aFor);
+  
+  // SVG DOM text methods:
+  uint32_t GetNumberOfChars(nsIContent* aContent);
+  float GetComputedTextLength(nsIContent* aContent);
+  float GetSubStringLength(nsIContent* aContent, uint32_t charnum, uint32_t nchars);
+  int32_t GetCharNumAtPosition(nsIContent* aContent, mozilla::nsISVGPoint* point);
+
+  nsresult GetStartPositionOfChar(nsIContent* aContent, uint32_t aCharNum,
+                                  mozilla::nsISVGPoint** aResult);
+  nsresult GetEndPositionOfChar(nsIContent* aContent, uint32_t aCharNum,
+                                mozilla::nsISVGPoint** aResult);
+  nsresult GetExtentOfChar(nsIContent* aContent, uint32_t aCharNum,
+                           nsIDOMSVGRect** aResult);
+  nsresult GetRotationOfChar(nsIContent* aContent, uint32_t aCharNum,
+                             float* aResult);
+
+  // nsSVGTextFrame2 methods:
+
+  /**
+   * Schedules mPositions to be recomputed and the covered region to be
+   * updated.
+   */
+  void NotifyGlyphMetricsChange();
+
+  /**
+   * Updates the mFontSizeScaleFactor value by looking at the range of
+   * font-sizes used within the <text>.
+   */
+  void UpdateFontSizeScaleFactor(bool aForceGlobalTransform);
+
+  double GetFontSizeScaleFactor() const;
+
+private:
+  /**
+   * This class exists purely because it would be too messy to pass the "for"
+   * flag for GetCanvasTM through the call chains to the GetCanvasTM() call in
+   * UpdateFontSizeScaleFactor.
+   */
+  class AutoCanvasTMForMarker {
+  public:
+    AutoCanvasTMForMarker(nsSVGTextFrame2* aFrame, uint32_t aFor)
+      : mFrame(aFrame)
+    {
+      mOldFor = mFrame->mGetCanvasTMForFlag;
+      mFrame->mGetCanvasTMForFlag = aFor;
+    }
+    ~AutoCanvasTMForMarker()
+    {
+      // Default
+      mFrame->mGetCanvasTMForFlag = mOldFor;
+    }
+  private:
+    nsSVGTextFrame2* mFrame;
+    uint32_t mOldFor;
+  };
+
+  /**
+   * Mutation observer used to watch for text positioning attribute changes
+   * on descendent text content elements (like <tspan>s).
+   */
+  class MutationObserver : public nsStubMutationObserver {
+  public:
+    MutationObserver()
+      : mFrame(nullptr)
+    {
+    }
+
+    void StartObserving(nsSVGTextFrame2* aFrame)
+    {
+      NS_ASSERTION(!mFrame, "should not be observing yet!");
+      mFrame = aFrame;
+      aFrame->GetContent()->AddMutationObserver(this);
+    }
+
+    virtual ~MutationObserver()
+    {
+      if (mFrame) {
+        mFrame->GetContent()->RemoveMutationObserver(this);
+      }
+    }
+
+    // nsISupports
+    NS_DECL_ISUPPORTS
+
+    // nsIMutationObserver
+    NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
+    NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
+    NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
+
+  private:
+    nsSVGTextFrame2* mFrame;
+  };
+
+  /**
+   * Reflows the anonymous block child.
+   */
+  void DoReflow(bool aForceGlobalTransform);
+
+  /**
+   * Calls FrameNeedsReflow on the anonymous block child.
+   */
+  void RequestReflow(nsIPresShell::IntrinsicDirty aType, uint32_t aBit);
+
+  /**
+   * Reflows the anonymous block child and recomputes mPositions if needed.
+   *
+   * @param aForceGlobalTransform passed down to UpdateFontSizeScaleFactor to
+   * control whether it should use the global transform even when
+   * NS_STATE_NONDISPLAY_CHILD
+   */
+  void UpdateGlyphPositioning(bool aForceGlobalTransform);
+
+  /**
+   * Populates mPositions with positioning information for each character
+   * within the <text>.
+   */
+  void DoGlyphPositioning();
+
+  /**
+   * Recursive helper for ResolvePositions below.
+   *
+   * @param aContent The current node.
+   * @param aIndex The current character index.
+   * @param aInTextPath Whether we are currently under a <textPath> element.
+   * @param aForceStartOfChunk Whether the next character we find should start a
+   *   new anchored chunk.
+   * @return The character index we got up to.
+   */
+  uint32_t ResolvePositions(nsIContent* aContent, uint32_t aIndex,
+                            bool aInTextPath, bool& aForceStartOfChunk,
+                            nsTArray<gfxPoint>& aDeltas);
+
+  /**
+   * Initializes mPositions with character position information based on
+   * x/y/rotate attributes, leaving unspecified values in the array if a position
+   * was not given for that character.  Also fills aDeltas with values based on
+   * dx/dy attributes.
+   *
+   * @return True if we recorded any positions.
+   */
+  bool ResolvePositions(nsTArray<gfxPoint>& aDeltas);
+
+  /**
+   * Determines the position, in app units, of each character in the <text> as
+   * laid out by reflow, and appends them to aPositions.  Any characters that
+   * are undisplayed or trimmed away just get the last position.
+   */
+  void DetermineCharPositions(nsTArray<nsPoint>& aPositions);
+
+  /**
+   * Sets mStartOfChunk to true for each character in mPositions that starts a
+   * line of text.
+   */
+  void AdjustChunksForLineBreaks();
+
+  /**
+   * Adjusts recorded character positions in mPositions to account for glyph
+   * boundaries.  Four things are done:
+   *
+   *   1. mClusterOrLigatureGroupMiddle is set to true for all such characters.
+   *
+   *   2. Any run and anchored chunk boundaries that begin in the middle of a
+   *      cluster/ligature group get moved to the start of the next
+   *      cluster/ligature group.
+   *
+   *   3. The position of any character in the middle of a cluster/ligature
+   *      group is updated to take into account partial ligatures and any
+   *      rotation the glyph as a whole has.  (The values that come out of
+   *      DetermineCharPositions which then get written into mPositions in
+   *      ResolvePositions store the same position value for each part of the
+   *      ligature.)
+   *
+   *   4. The rotation of any character in the middle of a cluster/ligature
+   *      group is set to the rotation of the first character.
+   */
+  void AdjustPositionsForClusters();
+
+  /**
+   * Updates the character positions stored in mPositions to account for
+   * text anchoring.
+   */
+  void DoAnchoring();
+
+  /**
+   * Updates character positions in mPositions for those characters inside a
+   * <textPath>.
+   */
+  void DoTextPathLayout();
+
+  /**
+   * Returns whether we need to render the text using
+   * nsTextFrame::DrawPathCallbacks rather than directly painting
+   * the text frames.
+   */
+  bool ShouldRenderAsPath(nsRenderingContext* aContext, nsTextFrame* aFrame);
+
+  // Methods to get information for a <textPath> frame.
+  nsIFrame* GetTextPathPathFrame(nsIFrame* aTextPathFrame);
+  already_AddRefed<gfxFlattenedPath> GetFlattenedTextPath(nsIFrame* aTextPathFrame);
+  gfxFloat GetOffsetScale(nsIFrame* aTextPathFrame);
+  gfxFloat GetStartOffset(nsIFrame* aTextPathFrame);
+
+  /**
+   * The MutationObserver we have registered for the <text> element subtree.
+   */
+  MutationObserver mMutationObserver;
+
+  /**
+   * Cached canvasTM value.
+   */
+  nsAutoPtr<gfxMatrix> mCanvasTM;
+
+  /**
+   * The number of characters in the DOM after the final nsTextFrame.  For
+   * example, with
+   *
+   *   <text>abcd<tspan display="none">ef</tspan></text>
+   *
+   * mTrailingUndisplayedCharacters would be 2.
+   */
+  uint32_t mTrailingUndisplayedCharacters;
+
+  /**
+   * Computed position information for each DOM character within the <text>.
+   */
+  nsTArray<mozilla::CharPosition> mPositions;
+
+  /**
+   * mFontSizeScaleFactor is used to cause the nsTextFrames to create text
+   * runs with a font size different from the actual font-size property value.
+   * This is used so that, for example with:
+   *
+   *   <svg>
+   *     <g transform="scale(2)">
+   *       <text font-size="10">abc</text>
+   *     </g>
+   *  </svg>
+   *
+   * a font size of 20 would be used.  It's preferable to use a font size that
+   * is identical or close to the size that the text will appear on the screen,
+   * because at very small or large font sizes, text metrics will be computed
+   * differently due to the limited precision that text runs have.
+   *
+   * mFontSizeScaleFactor is the amount the actual font-size property value
+   * should be multiplied by to cause the text run font size to (a) be within a
+   * "reasonable" range, and (b) be close to the actual size to be painted on
+   * screen.  (The "reasonable" range as determined by some #defines in
+   * nsSVGTextFrame2.cpp is 8..200.)
+   */
+  float mFontSizeScaleFactor;
+
+  /**
+   * The flag to pass to GetCanvasTM from UpdateFontSizeScaleFactor.  This is
+   * normally FOR_OUTERSVG_TM, but while painting or hit testing a pattern or
+   * marker, we set it to FOR_PAINTING or FOR_HIT_TESTING appropriately.
+   */
+  uint32_t mGetCanvasTMForFlag;
+
+  /**
+   * Whether something has changed to invalidate the values in mPositions.
+   */
+  bool mPositioningDirty;
+};
+
+#endif