Bug 1357545 - handle text-shadows/decorations with webrender (layers-free) r?jrmuizel draft
authorAlexis Beingessner <a.beingessner@gmail.com>
Mon, 19 Jun 2017 10:58:28 -0400
changeset 648494 eee2be5dbca2185a8bece3a8369a2a0df42733b2
parent 648384 e365137fa61bfd729617ba1ebf9f1ed79facd1f2
child 726841 be5fc9e38ead8bfdb1d1e54db7138c71293f8aac
push id74774
push userbmo:a.beingessner@gmail.com
push dateThu, 17 Aug 2017 20:55:45 +0000
reviewersjrmuizel
bugs1357545
milestone57.0a1
Bug 1357545 - handle text-shadows/decorations with webrender (layers-free) r?jrmuizel This replaces our DrawTargetCapture hack with a similar but more powerful TextDrawTarget hack. The old design had several limitations: * It couldn't handle shadows * It couldn't handle selections * It couldn't handle font/color changes in a single text-run * It couldn't handle decorations (underline, overline, line-through) Mostly this was a consequence of the fact that it only modified the start and end of the rendering algorithm, and therefore couldn't distinguish draw calls for different parts of the text. This new design is based on a similar principle as DrawTargetCapture, but also passes down the TextDrawTarget in the drawing arguments, so that the drawing algorithm can notify us of changes in phase (e.g. "now we're doing underlines"). This also lets us directly pass data to TextDrawTarget when possible (as is done for shadows and selections). In doing this, I also improved the logic copied from ContainsOnlyColoredGlyphs to handle changes in font/color mid-text-run (which can happen because of font fallback). The end result is: * We handle all shadows natively * We handle all selections natively * We handle all decorations natively * We handle font/color changes in a single text-run * Although we still hackily intercept draw calls * But we don't need to buffer commands, reducing total memcopies In addition, this change integrates webrender's PushTextShadow and PushLine APIs, which were designed for this use case. This is only done in the layerless path; WebrenderTextLayer continues to be semantically limited, as we aren't actively maintaining non-layers-free webrender anymore. This also doesn't modify TextLayers, to minimize churn. In theory they can be augmented to support the richer semantics that TextDrawTarget has, but there's little motivation since the API is largely unused with this change. MozReview-Commit-ID: 4IjTsSW335h
gfx/layers/wr/WebRenderBridgeChild.cpp
gfx/layers/wr/WebRenderBridgeChild.h
gfx/layers/wr/WebRenderTextLayer.cpp
gfx/webrender_bindings/WebRenderAPI.cpp
gfx/webrender_bindings/WebRenderAPI.h
gfx/webrender_bindings/src/bindings.rs
gfx/webrender_bindings/webrender_ffi_generated.h
layout/generic/TextDrawTarget.h
layout/generic/moz.build
layout/generic/nsBulletFrame.cpp
layout/generic/nsTextFrame.cpp
layout/generic/nsTextFrame.h
layout/painting/nsCSSRendering.cpp
layout/painting/nsCSSRendering.h
--- a/gfx/layers/wr/WebRenderBridgeChild.cpp
+++ b/gfx/layers/wr/WebRenderBridgeChild.cpp
@@ -207,47 +207,41 @@ WriteFontFileData(const uint8_t* aData, 
     return;
   }
   memcpy(data->mFontBuffer.mData, aData, aLength);
 
   data->mFontIndex = aIndex;
 }
 
 void
-WebRenderBridgeChild::PushGlyphs(wr::DisplayListBuilder& aBuilder, const nsTArray<GlyphArray>& aGlyphs,
-                                 gfx::ScaledFont* aFont, const StackingContextHelper& aSc,
+WebRenderBridgeChild::PushGlyphs(wr::DisplayListBuilder& aBuilder, const nsTArray<gfx::Glyph>& aGlyphs,
+                                 gfx::ScaledFont* aFont, const gfx::Color& aColor, const StackingContextHelper& aSc,
                                  const LayerRect& aBounds, const LayerRect& aClip)
 {
   MOZ_ASSERT(aFont);
   MOZ_ASSERT(!aGlyphs.IsEmpty());
 
   wr::WrFontKey key = GetFontKeyForScaledFont(aFont);
   MOZ_ASSERT(key.mNamespace.mHandle && key.mHandle);
 
-  for (size_t i = 0; i < aGlyphs.Length(); i++) {
-    GlyphArray glyph_array = aGlyphs[i];
-    nsTArray<gfx::Glyph>& glyphs = glyph_array.glyphs();
-
-    nsTArray<wr::GlyphInstance> wr_glyph_instances;
-    wr_glyph_instances.SetLength(glyphs.Length());
+  nsTArray<wr::GlyphInstance> wr_glyph_instances;
+  wr_glyph_instances.SetLength(aGlyphs.Length());
 
-    for (size_t j = 0; j < glyphs.Length(); j++) {
-      wr_glyph_instances[j].index = glyphs[j].mIndex;
-      wr_glyph_instances[j].point = aSc.ToRelativeLayoutPoint(
-              LayerPoint::FromUnknownPoint(glyphs[j].mPosition));
-    }
+  for (size_t j = 0; j < aGlyphs.Length(); j++) {
+    wr_glyph_instances[j].index = aGlyphs[j].mIndex;
+    wr_glyph_instances[j].point = aSc.ToRelativeLayoutPoint(
+            LayerPoint::FromUnknownPoint(aGlyphs[j].mPosition));
+  }
 
-    aBuilder.PushText(aSc.ToRelativeLayoutRect(aBounds),
-                      aSc.ToRelativeLayoutRect(aClip),
-                      glyph_array.color().value(),
-                      key,
-                      Range<const wr::GlyphInstance>(wr_glyph_instances.Elements(), wr_glyph_instances.Length()),
-                      aFont->GetSize());
-
-  }
+  aBuilder.PushText(aSc.ToRelativeLayoutRect(aBounds),
+                    aSc.ToRelativeLayoutRect(aClip),
+                    aColor,
+                    key,
+                    Range<const wr::GlyphInstance>(wr_glyph_instances.Elements(), wr_glyph_instances.Length()),
+                    aFont->GetSize());
 }
 
 wr::FontKey
 WebRenderBridgeChild::GetFontKeyForScaledFont(gfx::ScaledFont* aScaledFont)
 {
   MOZ_ASSERT(!mDestroyed);
   MOZ_ASSERT(aScaledFont);
   MOZ_ASSERT((aScaledFont->GetType() == gfx::FontType::DWRITE) ||
--- a/gfx/layers/wr/WebRenderBridgeChild.h
+++ b/gfx/layers/wr/WebRenderBridgeChild.h
@@ -99,18 +99,19 @@ public:
     mIdNamespace = aIdNamespace;
   }
 
   wr::WrImageKey GetNextImageKey()
   {
     return wr::WrImageKey{ GetNamespace(), GetNextResourceId() };
   }
 
-  void PushGlyphs(wr::DisplayListBuilder& aBuilder, const nsTArray<GlyphArray>& aGlyphs,
-                  gfx::ScaledFont* aFont, const StackingContextHelper& aSc,
+  void PushGlyphs(wr::DisplayListBuilder& aBuilder, const nsTArray<gfx::Glyph>& aGlyphs,
+                  gfx::ScaledFont* aFont, const gfx::Color& aColor,
+                  const StackingContextHelper& aSc,
                   const LayerRect& aBounds, const LayerRect& aClip);
 
   wr::FontKey GetFontKeyForScaledFont(gfx::ScaledFont* aScaledFont);
 
   void RemoveExpiredFontKeys();
   void ClearReadLocks();
 
   void BeginClearCachedResources();
--- a/gfx/layers/wr/WebRenderTextLayer.cpp
+++ b/gfx/layers/wr/WebRenderTextLayer.cpp
@@ -35,13 +35,16 @@ WebRenderTextLayer::RenderLayer(wr::Disp
         // WebRender compositing, we don't pass the transform on this layer to
         // WR, so WR has no way of knowing about the transformed bounds unless
         // we apply it here. The glyphs that we push to WR should already be
         // taking the transform into account.
         GetTransform().TransformBounds(IntRectToRect(mBounds))
     );
     DumpLayerInfo("TextLayer", rect);
 
-    WrBridge()->PushGlyphs(aBuilder, mGlyphs, mFont, aSc, rect, rect);
+    for (GlyphArray& glyphs : mGlyphs) {
+        WrBridge()->PushGlyphs(aBuilder, glyphs.glyphs(), mFont,
+                               glyphs.color().value(), aSc, rect, rect);
+    }
 }
 
 } // namespace layers
 } // namespace mozilla
--- a/gfx/webrender_bindings/WebRenderAPI.cpp
+++ b/gfx/webrender_bindings/WebRenderAPI.cpp
@@ -958,16 +958,55 @@ DisplayListBuilder::PushText(const wr::L
   wr_dp_push_text(mWrState, aBounds, aClip,
                   ToColorF(aColor),
                   aFontKey,
                   &aGlyphBuffer[0], aGlyphBuffer.length(),
                   aGlyphSize);
 }
 
 void
+DisplayListBuilder::PushLine(const wr::LayoutRect& aClip,
+                             const wr::Line& aLine)
+{
+ wr_dp_push_line(mWrState, aClip, aLine.baseline, aLine.start, aLine.end,
+                 aLine.orientation, aLine.width, aLine.color, aLine.style);
+
+/* TODO(Gankro): remove this
+  LayoutRect rect;
+  if (aLine.orientation == wr::LineOrientation::Horizontal) {
+    rect.origin.x = aLine.start;
+    rect.origin.y = aLine.baseline;
+    rect.size.width = aLine.end - aLine.start;
+    rect.size.height = aLine.width;
+  } else {
+    rect.origin.x = aLine.baseline;
+    rect.origin.y = aLine.start;
+    rect.size.width = aLine.width;
+    rect.size.height = aLine.end - aLine.start;
+  }
+
+  PushRect(rect, aClip, aLine.color);
+*/
+}
+
+void
+DisplayListBuilder::PushTextShadow(const wr::LayoutRect& aRect,
+                                   const wr::LayoutRect& aClip,
+                                   const wr::TextShadow& aShadow)
+{
+  wr_dp_push_text_shadow(mWrState, aRect, aClip, aShadow);
+}
+
+void
+DisplayListBuilder::PopTextShadow()
+{
+  wr_dp_pop_text_shadow(mWrState);
+}
+
+void
 DisplayListBuilder::PushBoxShadow(const wr::LayoutRect& aRect,
                                   const wr::LayoutRect& aClip,
                                   const wr::LayoutRect& aBoxBounds,
                                   const wr::LayoutVector2D& aOffset,
                                   const wr::ColorF& aColor,
                                   const float& aBlurRadius,
                                   const float& aSpreadRadius,
                                   const float& aBorderRadius,
--- a/gfx/webrender_bindings/WebRenderAPI.h
+++ b/gfx/webrender_bindings/WebRenderAPI.h
@@ -31,16 +31,28 @@ class WebRenderBridgeParent;
 }
 
 namespace wr {
 
 class DisplayListBuilder;
 class RendererOGL;
 class RendererEvent;
 
+// This isn't part of WR's API, but we define it here to simplify layout's
+// logic and data plumbing.
+struct Line {
+  float baseline;
+  float start;
+  float end;
+  float width;
+  wr::ColorF color;
+  wr::LineOrientation orientation;
+  wr::LineStyle style;
+};
+
 class WebRenderAPI
 {
   NS_INLINE_DECL_REFCOUNTING(WebRenderAPI);
 
 public:
   /// This can be called on the compositor thread only.
   static already_AddRefed<WebRenderAPI> Create(bool aEnableProfiler,
                                                layers::CompositorBridgeParentBase* aBridge,
@@ -290,16 +302,27 @@ public:
 
   void PushText(const wr::LayoutRect& aBounds,
                 const wr::LayoutRect& aClip,
                 const gfx::Color& aColor,
                 wr::FontKey aFontKey,
                 Range<const wr::GlyphInstance> aGlyphBuffer,
                 float aGlyphSize);
 
+  void PushLine(const wr::LayoutRect& aClip,
+                const wr::Line& aLine);
+
+  void PushTextShadow(const wr::LayoutRect& aBounds,
+                      const wr::LayoutRect& aClip,
+                      const wr::TextShadow& aShadow);
+
+  void PopTextShadow();
+
+
+
   void PushBoxShadow(const wr::LayoutRect& aRect,
                      const wr::LayoutRect& aClip,
                      const wr::LayoutRect& aBoxBounds,
                      const wr::LayoutVector2D& aOffset,
                      const wr::ColorF& aColor,
                      const float& aBlurRadius,
                      const float& aSpreadRadius,
                      const float& aBorderRadius,
--- a/gfx/webrender_bindings/src/bindings.rs
+++ b/gfx/webrender_bindings/src/bindings.rs
@@ -527,17 +527,17 @@ pub struct WrThreadPool(Arc<rayon::Threa
 #[no_mangle]
 pub unsafe extern "C" fn wr_thread_pool_new() -> *mut WrThreadPool {
     let worker_config = rayon::Configuration::new()
         .thread_name(|idx|{ format!("WebRender:Worker#{}", idx) })
         .start_handler(|idx| {
             register_thread_with_profiler(format!("WebRender:Worker#{}", idx));
         });
 
-    let workers = Arc::new(rayon::ThreadPool::new(worker_config).unwrap());        
+    let workers = Arc::new(rayon::ThreadPool::new(worker_config).unwrap());
 
     Box::into_raw(Box::new(WrThreadPool(workers)))
 }
 
 /// cbindgen:postfix=WR_DESTRUCTOR_SAFE_FUNC
 #[no_mangle]
 pub unsafe extern "C" fn wr_thread_pool_delete(thread_pool: *mut WrThreadPool) {
     Box::from_raw(thread_pool);
@@ -1277,16 +1277,58 @@ pub extern "C" fn wr_dp_push_text(state:
                     &glyph_slice,
                     font_key,
                     colorf,
                     Au::from_f32_px(glyph_size),
                     glyph_options);
 }
 
 #[no_mangle]
+pub extern "C" fn wr_dp_push_text_shadow(state: &mut WrState,
+                                         bounds: LayoutRect,
+                                         clip: LayoutRect,
+                                         shadow: TextShadow) {
+    assert!(unsafe { is_in_main_thread() });
+
+    state.frame_builder.dl_builder.push_text_shadow(bounds, Some(LocalClip::Rect(clip.into())), shadow.into());
+}
+
+#[no_mangle]
+pub extern "C" fn wr_dp_pop_text_shadow(state: &mut WrState) {
+    assert!(unsafe { is_in_main_thread() });
+
+    state.frame_builder.dl_builder.pop_text_shadow();
+}
+
+#[no_mangle]
+pub extern "C" fn wr_dp_push_line(state: &mut WrState,
+                                  clip: LayoutRect,
+                                  baseline: f32,
+                                  start: f32,
+                                  end: f32,
+                                  orientation: LineOrientation,
+                                  width: f32,
+                                  color: ColorF,
+                                  style: LineStyle) {
+    assert!(unsafe { is_in_main_thread() });
+
+    state.frame_builder
+         .dl_builder
+         .push_line(Some(LocalClip::Rect(clip.into())),
+                    baseline,
+                    start,
+                    end,
+                    orientation,
+                    width,
+                    color,
+                    style);
+
+}
+
+#[no_mangle]
 pub extern "C" fn wr_dp_push_border(state: &mut WrState,
                                     rect: LayoutRect,
                                     clip: LayoutRect,
                                     widths: BorderWidths,
                                     top: BorderSide,
                                     right: BorderSide,
                                     bottom: BorderSide,
                                     left: BorderSide,
--- a/gfx/webrender_bindings/webrender_ffi_generated.h
+++ b/gfx/webrender_bindings/webrender_ffi_generated.h
@@ -72,16 +72,32 @@ enum class ImageFormat : uint32_t {
 enum class ImageRendering : uint32_t {
   Auto = 0,
   CrispEdges = 1,
   Pixelated = 2,
 
   Sentinel /* this must be last for serialization purposes. */
 };
 
+enum class LineOrientation : uint8_t {
+  Vertical = 0,
+  Horizontal = 1,
+
+  Sentinel /* this must be last for serialization purposes. */
+};
+
+enum class LineStyle : uint8_t {
+  Solid = 0,
+  Dotted = 1,
+  Dashed = 2,
+  Wavy = 3,
+
+  Sentinel /* this must be last for serialization purposes. */
+};
+
 enum class MixBlendMode : uint32_t {
   Normal = 0,
   Multiply = 1,
   Screen = 2,
   Overlay = 3,
   Darken = 4,
   Lighten = 5,
   ColorDodge = 6,
@@ -551,16 +567,28 @@ struct GlyphInstance {
   LayoutPoint point;
 
   bool operator==(const GlyphInstance& aOther) const {
     return index == aOther.index &&
            point == aOther.point;
   }
 };
 
+struct TextShadow {
+  LayoutVector2D offset;
+  ColorF color;
+  float blur_radius;
+
+  bool operator==(const TextShadow& aOther) const {
+    return offset == aOther.offset &&
+           color == aOther.color &&
+           blur_radius == aOther.blur_radius;
+  }
+};
+
 typedef YuvColorSpace WrYuvColorSpace;
 
 struct MutByteSlice {
   uint8_t *buffer;
   size_t len;
 
   bool operator==(const MutByteSlice& aOther) const {
     return buffer == aOther.buffer &&
@@ -796,16 +824,20 @@ WR_INLINE
 void wr_dp_pop_scroll_layer(WrState *aState)
 WR_FUNC;
 
 WR_INLINE
 void wr_dp_pop_stacking_context(WrState *aState)
 WR_FUNC;
 
 WR_INLINE
+void wr_dp_pop_text_shadow(WrState *aState)
+WR_FUNC;
+
+WR_INLINE
 void wr_dp_push_border(WrState *aState,
                        LayoutRect aRect,
                        LayoutRect aClip,
                        BorderWidths aWidths,
                        BorderSide aTop,
                        BorderSide aRight,
                        BorderSide aBottom,
                        BorderSide aLeft,
@@ -892,16 +924,28 @@ void wr_dp_push_image(WrState *aState,
                       LayoutRect aClip,
                       LayoutSize aStretchSize,
                       LayoutSize aTileSpacing,
                       ImageRendering aImageRendering,
                       WrImageKey aKey)
 WR_FUNC;
 
 WR_INLINE
+void wr_dp_push_line(WrState *aState,
+                     LayoutRect aClip,
+                     float aBaseline,
+                     float aStart,
+                     float aEnd,
+                     LineOrientation aOrientation,
+                     float aWidth,
+                     ColorF aColor,
+                     LineStyle aStyle)
+WR_FUNC;
+
+WR_INLINE
 void wr_dp_push_linear_gradient(WrState *aState,
                                 LayoutRect aRect,
                                 LayoutRect aClip,
                                 LayoutPoint aStartPoint,
                                 LayoutPoint aEndPoint,
                                 const GradientStop *aStops,
                                 size_t aStopsCount,
                                 ExtendMode aExtendMode,
@@ -956,16 +1000,23 @@ void wr_dp_push_text(WrState *aState,
                      ColorF aColor,
                      WrFontKey aFontKey,
                      const GlyphInstance *aGlyphs,
                      uint32_t aGlyphCount,
                      float aGlyphSize)
 WR_FUNC;
 
 WR_INLINE
+void wr_dp_push_text_shadow(WrState *aState,
+                            LayoutRect aBounds,
+                            LayoutRect aClip,
+                            TextShadow aShadow)
+WR_FUNC;
+
+WR_INLINE
 void wr_dp_push_yuv_NV12_image(WrState *aState,
                                LayoutRect aBounds,
                                LayoutRect aClip,
                                WrImageKey aImageKey0,
                                WrImageKey aImageKey1,
                                WrYuvColorSpace aColorSpace,
                                ImageRendering aImageRendering)
 WR_FUNC;
new file mode 100644
--- /dev/null
+++ b/layout/generic/TextDrawTarget.h
@@ -0,0 +1,458 @@
+/* -*- 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 TextDrawTarget_h
+#define TextDrawTarget_h
+
+#include "mozilla/gfx/2D.h"
+
+namespace mozilla {
+namespace layout {
+
+using namespace gfx;
+
+// This is used by all Advanced Layers users, so we use plain gfx types
+struct TextRunFragment {
+  ScaledFont* font;
+  Color color;
+  nsTArray<gfx::Glyph> glyphs;
+};
+
+// Only webrender handles this, so we use webrender types
+struct SelectionFragment {
+  wr::ColorF color;
+  wr::LayoutRect rect;
+};
+
+// This class is fake DrawTarget, used to intercept text draw calls, while
+// also collecting up the other aspects of text natively.
+//
+// When using advanced-layers in nsDisplayText's constructor, we construct this
+// and run the full painting algorithm with this as the DrawTarget. This is
+// done to avoid having to massively refactor gecko's text painting code (which
+// has lots of components shared between other rendering algorithms).
+//
+// In some phases of the painting algorithm, we can grab the relevant values
+// and feed them directly into TextDrawTarget. For instance, selections,
+// decorations, and shadows are handled in this manner. In those cases we can
+// also short-circuit the painting algorithm to save work.
+//
+// In other phases, the computed values are sufficiently buried in complex
+// code that it's best for us to just intercept the final draw calls. This
+// is how we handle computing the glyphs of the main text and text-emphasis
+// (see our overloaded FillGlyphs implementation).
+//
+// To be clear: this is a big hack. With time we hope to refactor the codebase
+// so that all the elements of text are handled directly by TextDrawTarget,
+// which is to say everything is done like we do selections and shadows now.
+// This design is a good step for doing this work incrementally.
+//
+// This is also likely to be a bit buggy (missing or misinterpreted info)
+// while we further develop the design.
+//
+// This does not currently support SVG text effects.
+class TextDrawTarget : public DrawTarget
+{
+public:
+  // The different phases of drawing the text we're in
+  // Each should only happen once, and in the given order.
+  enum class Phase : uint8_t {
+    eSelection, eUnderline, eOverline, eGlyphs, eEmphasisMarks, eLineThrough
+  };
+
+  explicit TextDrawTarget()
+  : mCurrentlyDrawing(Phase::eSelection)
+  {
+    mCurrentTarget = gfx::Factory::CreateDrawTarget(gfx::BackendType::SKIA, IntSize(1, 1), gfx::SurfaceFormat::B8G8R8A8);
+  }
+
+  // Prevent this from being copied
+  TextDrawTarget(const TextDrawTarget& src) = delete;
+  TextDrawTarget& operator=(const TextDrawTarget&) = delete;
+
+  // Change the phase of text we're drawing.
+  void StartDrawing(Phase aPhase) { mCurrentlyDrawing = aPhase; }
+
+  // This overload just stores the glyphs/font/color.
+  void
+  FillGlyphs(ScaledFont* aFont,
+             const GlyphBuffer& aBuffer,
+             const Pattern& aPattern,
+             const DrawOptions& aOptions,
+             const GlyphRenderingOptions* aRenderingOptions) override
+  {
+    // FIXME: figure out which of these asserts are real
+    MOZ_RELEASE_ASSERT(aOptions.mCompositionOp == CompositionOp::OP_OVER);
+    MOZ_RELEASE_ASSERT(aOptions.mAlpha == 1.0f);
+
+    // Make sure we're only given color patterns
+    MOZ_RELEASE_ASSERT(aPattern.GetType() == PatternType::COLOR);
+    const ColorPattern* colorPat = static_cast<const ColorPattern*>(&aPattern);
+
+    // Make sure the font exists
+    MOZ_RELEASE_ASSERT(aFont);
+
+    // FIXME(?): Deal with AA on the DrawOptions, and the GlyphRenderingOptions
+
+    if (mCurrentlyDrawing != Phase::eGlyphs &&
+        mCurrentlyDrawing != Phase::eEmphasisMarks) {
+      MOZ_CRASH("TextDrawTarget received glyphs in wrong phase");
+    }
+
+    // We need to push a new TextRunFragment whenever the font/color changes
+    // (usually this implies some font fallback from mixing languages/emoji)
+    TextRunFragment* fragment;
+    if (mText.IsEmpty() ||
+        mText.LastElement().font != aFont ||
+        mText.LastElement().color != colorPat->mColor) {
+      fragment = mText.AppendElement();
+      fragment->font = aFont;
+      fragment->color = colorPat->mColor;
+    } else {
+      fragment = &mText.LastElement();
+    }
+
+    nsTArray<Glyph>& glyphs = fragment->glyphs;
+
+    size_t oldLength = glyphs.Length();
+    glyphs.SetLength(oldLength + aBuffer.mNumGlyphs);
+    PodCopy(glyphs.Elements() + oldLength, aBuffer.mGlyphs, aBuffer.mNumGlyphs);
+  }
+
+  void AppendShadow(const wr::TextShadow& aShadow) { mShadows.AppendElement(aShadow); }
+
+  void
+  AppendSelection(const LayoutDeviceRect& aRect, const Color& aColor)
+  {
+    SelectionFragment frag;
+    frag.rect = wr::ToLayoutRect(aRect);
+    frag.color = wr::ToColorF(aColor);
+    mSelections.AppendElement(frag);
+  }
+
+  void
+  AppendDecoration(const Point& aStart,
+                   const Point& aEnd,
+                   const float aThickness,
+                   const bool aVertical,
+                   const Color& aColor,
+                   const uint8_t aStyle)
+  {
+    wr::Line* decoration;
+
+    switch (mCurrentlyDrawing) {
+      case Phase::eUnderline:
+      case Phase::eOverline:
+        decoration = mBeforeDecorations.AppendElement();
+        break;
+      case Phase::eLineThrough:
+        decoration = mAfterDecorations.AppendElement();
+        break;
+      default:
+        MOZ_CRASH("TextDrawTarget received Decoration in wrong phase");
+    }
+
+    // This function is basically designed to slide into the decoration drawing
+    // code of nsCSSRendering with minimum disruption, to minimize the
+    // chances of implementation drift. As such, it mostly looks like a call
+    // to a skia-style StrokeLine method: two end-points, with a thickness
+    // and style. Notably the end-points are *centered* in the block direction,
+    // even though webrender wants a rect-like representation, where the points
+    // are on corners.
+    //
+    // So we mangle the format here in a single centralized place, where neither
+    // webrender nor nsCSSRendering has to care about this mismatch.
+    decoration->baseline = (aVertical ? aStart.x : aStart.y) - aThickness / 2;
+    decoration->start = aVertical ? aStart.y : aStart.x;
+    decoration->end = aVertical ? aEnd.y : aEnd.x;
+    decoration->width = aThickness;
+    decoration->color = wr::ToColorF(aColor);
+    decoration->orientation = aVertical
+      ? wr::LineOrientation::Vertical
+      : wr::LineOrientation::Horizontal;
+
+    switch (aStyle) {
+      case NS_STYLE_TEXT_DECORATION_STYLE_SOLID:
+        decoration->style = wr::LineStyle::Solid;
+        break;
+      case NS_STYLE_TEXT_DECORATION_STYLE_DOTTED:
+        decoration->style = wr::LineStyle::Dotted;
+        break;
+      case NS_STYLE_TEXT_DECORATION_STYLE_DASHED:
+        decoration->style = wr::LineStyle::Dashed;
+        break;
+      case NS_STYLE_TEXT_DECORATION_STYLE_WAVY:
+        decoration->style = wr::LineStyle::Wavy;
+        break;
+      // Double lines should be lowered to two solid lines
+      case NS_STYLE_TEXT_DECORATION_STYLE_DOUBLE:
+      default:
+        MOZ_CRASH("TextDrawTarget received unsupported line style");
+    }
+
+
+  }
+
+  const nsTArray<wr::TextShadow>& GetShadows() { return mShadows; }
+  const nsTArray<TextRunFragment>& GetText() { return mText; }
+  const nsTArray<SelectionFragment>& GetSelections() { return mSelections; }
+  const nsTArray<wr::Line>& GetBeforeDecorations() { return mBeforeDecorations; }
+  const nsTArray<wr::Line>& GetAfterDecorations() { return mAfterDecorations; }
+
+  bool
+  CanSerializeFonts()
+  {
+    for (const TextRunFragment& frag : GetText()) {
+      if (!frag.font->CanSerialize()) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  bool
+  TryMerge(const TextDrawTarget& other) {
+    if (mShadows != other.mShadows) {
+      return false;
+    }
+
+    mText.AppendElements(other.mText);
+    mSelections.AppendElements(other.mSelections);
+    mBeforeDecorations.AppendElements(other.mBeforeDecorations);
+    mAfterDecorations.AppendElements(other.mAfterDecorations);
+
+    return true;
+  }
+
+
+private:
+  // The part of the text we're currently drawing (glyphs, underlines, etc.)
+  Phase mCurrentlyDrawing;
+
+  // Properties of the whole text
+  nsTArray<wr::TextShadow> mShadows;
+  nsTArray<TextRunFragment> mText;
+  nsTArray<SelectionFragment> mSelections;
+  nsTArray<wr::Line> mBeforeDecorations;
+  nsTArray<wr::Line> mAfterDecorations;
+
+  // A dummy to handle parts of the DrawTarget impl we don't care for
+  RefPtr<DrawTarget> mCurrentTarget;
+
+  // The rest of this is dummy implementations of DrawTarget's API
+public:
+  DrawTargetType GetType() const override {
+    return mCurrentTarget->GetType();
+  }
+
+  BackendType GetBackendType() const override {
+    return mCurrentTarget->GetBackendType();
+  }
+
+  bool IsRecording() const override { return true; }
+  bool IsCaptureDT() const override { return false; }
+
+  already_AddRefed<SourceSurface> Snapshot() override {
+    return mCurrentTarget->Snapshot();
+  }
+
+  already_AddRefed<SourceSurface> IntoLuminanceSource(LuminanceType aLuminanceType,
+                                                      float aOpacity) override {
+    return mCurrentTarget->IntoLuminanceSource(aLuminanceType, aOpacity);
+  }
+
+  IntSize GetSize() override {
+    return mCurrentTarget->GetSize();
+  }
+
+  void Flush() override {
+    mCurrentTarget->Flush();
+  }
+
+  void DrawCapturedDT(DrawTargetCapture *aCaptureDT,
+                      const Matrix& aTransform) override {
+    mCurrentTarget->DrawCapturedDT(aCaptureDT, aTransform);
+  }
+
+  void DrawSurface(SourceSurface *aSurface,
+                   const Rect &aDest,
+                   const Rect &aSource,
+                   const DrawSurfaceOptions &aSurfOptions,
+                   const DrawOptions &aOptions) override {
+    mCurrentTarget->DrawSurface(aSurface, aDest, aSource, aSurfOptions, aOptions);
+  }
+
+  void DrawFilter(FilterNode *aNode,
+                          const Rect &aSourceRect,
+                          const Point &aDestPoint,
+                          const DrawOptions &aOptions) override {
+    mCurrentTarget->DrawFilter(aNode, aSourceRect, aDestPoint, aOptions);
+  }
+
+  void DrawSurfaceWithShadow(SourceSurface *aSurface,
+                             const Point &aDest,
+                             const Color &aColor,
+                             const Point &aOffset,
+                             Float aSigma,
+                             CompositionOp aOperator) override {
+    mCurrentTarget->DrawSurfaceWithShadow(aSurface, aDest, aColor, aOffset, aSigma, aOperator);
+  }
+
+  void ClearRect(const Rect &aRect) override {
+    mCurrentTarget->ClearRect(aRect);
+  }
+
+  void CopySurface(SourceSurface *aSurface,
+                   const IntRect &aSourceRect,
+                   const IntPoint &aDestination) override {
+    mCurrentTarget->CopySurface(aSurface, aSourceRect, aDestination);
+  }
+
+  void FillRect(const Rect &aRect,
+                const Pattern &aPattern,
+                const DrawOptions &aOptions = DrawOptions()) override {
+    mCurrentTarget->FillRect(aRect, aPattern, aOptions);
+  }
+
+  void StrokeRect(const Rect &aRect,
+                  const Pattern &aPattern,
+                  const StrokeOptions &aStrokeOptions,
+                  const DrawOptions &aOptions) override {
+    mCurrentTarget->StrokeRect(aRect, aPattern, aStrokeOptions, aOptions);
+  }
+
+  void StrokeLine(const Point &aStart,
+                  const Point &aEnd,
+                  const Pattern &aPattern,
+                  const StrokeOptions &aStrokeOptions,
+                  const DrawOptions &aOptions) override {
+    mCurrentTarget->StrokeLine(aStart, aEnd, aPattern, aStrokeOptions, aOptions);
+  }
+
+
+  void Stroke(const Path *aPath,
+              const Pattern &aPattern,
+              const StrokeOptions &aStrokeOptions,
+              const DrawOptions &aOptions) override {
+    mCurrentTarget->Stroke(aPath, aPattern, aStrokeOptions, aOptions);
+  }
+
+  void Fill(const Path *aPath,
+            const Pattern &aPattern,
+            const DrawOptions &aOptions) override {
+    mCurrentTarget->Fill(aPath, aPattern, aOptions);
+  }
+
+  void StrokeGlyphs(ScaledFont* aFont,
+                    const GlyphBuffer& aBuffer,
+                    const Pattern& aPattern,
+                    const StrokeOptions& aStrokeOptions,
+                    const DrawOptions& aOptions,
+                    const GlyphRenderingOptions* aRenderingOptions) override {
+    MOZ_ASSERT(mCurrentlyDrawing == Phase::eGlyphs);
+    mCurrentTarget->StrokeGlyphs(aFont, aBuffer, aPattern,
+                                 aStrokeOptions, aOptions, aRenderingOptions);
+  }
+
+  void Mask(const Pattern &aSource,
+                    const Pattern &aMask,
+                    const DrawOptions &aOptions) override {
+    return mCurrentTarget->Mask(aSource, aMask, aOptions);
+  }
+
+  void MaskSurface(const Pattern &aSource,
+                   SourceSurface *aMask,
+                   Point aOffset,
+                   const DrawOptions &aOptions) override {
+    return mCurrentTarget->MaskSurface(aSource, aMask, aOffset, aOptions);
+  }
+
+  bool Draw3DTransformedSurface(SourceSurface* aSurface,
+                                const Matrix4x4& aMatrix) override {
+    return mCurrentTarget->Draw3DTransformedSurface(aSurface, aMatrix);
+  }
+
+  void PushClip(const Path *aPath) override {
+    mCurrentTarget->PushClip(aPath);
+  }
+
+  void PushClipRect(const Rect &aRect) override {
+    mCurrentTarget->PushClipRect(aRect);
+  }
+
+  void PushDeviceSpaceClipRects(const IntRect* aRects, uint32_t aCount) override {
+    mCurrentTarget->PushDeviceSpaceClipRects(aRects, aCount);
+  }
+
+  void PopClip() override {
+    mCurrentTarget->PopClip();
+  }
+
+  void PushLayer(bool aOpaque, Float aOpacity,
+                         SourceSurface* aMask,
+                         const Matrix& aMaskTransform,
+                         const IntRect& aBounds,
+                         bool aCopyBackground) override {
+    mCurrentTarget->PushLayer(aOpaque, aOpacity, aMask, aMaskTransform, aBounds, aCopyBackground);
+  }
+
+  void PopLayer() override {
+    mCurrentTarget->PopLayer();
+  }
+
+
+  already_AddRefed<SourceSurface> CreateSourceSurfaceFromData(unsigned char *aData,
+                                                              const IntSize &aSize,
+                                                              int32_t aStride,
+                                                              SurfaceFormat aFormat) const override {
+    return mCurrentTarget->CreateSourceSurfaceFromData(aData, aSize, aStride, aFormat);
+  }
+
+  already_AddRefed<SourceSurface> OptimizeSourceSurface(SourceSurface *aSurface) const override {
+      return mCurrentTarget->OptimizeSourceSurface(aSurface);
+  }
+
+  already_AddRefed<SourceSurface>
+    CreateSourceSurfaceFromNativeSurface(const NativeSurface &aSurface) const override {
+      return mCurrentTarget->CreateSourceSurfaceFromNativeSurface(aSurface);
+  }
+
+  already_AddRefed<DrawTarget>
+    CreateSimilarDrawTarget(const IntSize &aSize, SurfaceFormat aFormat) const override {
+      return mCurrentTarget->CreateSimilarDrawTarget(aSize, aFormat);
+  }
+
+  already_AddRefed<PathBuilder> CreatePathBuilder(FillRule aFillRule) const override {
+    return mCurrentTarget->CreatePathBuilder(aFillRule);
+  }
+
+  already_AddRefed<FilterNode> CreateFilter(FilterType aType) override {
+    return mCurrentTarget->CreateFilter(aType);
+  }
+
+  already_AddRefed<GradientStops>
+    CreateGradientStops(GradientStop *aStops,
+                        uint32_t aNumStops,
+                        ExtendMode aExtendMode) const override {
+      return mCurrentTarget->CreateGradientStops(aStops, aNumStops, aExtendMode);
+  }
+
+  void SetTransform(const Matrix &aTransform) override {
+    mCurrentTarget->SetTransform(aTransform);
+    // Need to do this to make inherited GetTransform to work
+    DrawTarget::SetTransform(aTransform);
+  }
+
+  void* GetNativeSurface(NativeSurfaceType aType) override {
+    return mCurrentTarget->GetNativeSurface(aType);
+  }
+
+  void DetachAllSnapshots() override { mCurrentTarget->DetachAllSnapshots(); }
+};
+
+}
+}
+
+#endif
--- a/layout/generic/moz.build
+++ b/layout/generic/moz.build
@@ -98,16 +98,17 @@ EXPORTS += [
     'nsRubyTextFrame.h',
     'nsSplittableFrame.h',
     'nsSubDocumentFrame.h',
     'nsTextFrameUtils.h',
     'nsTextRunTransformations.h',
     'RubyUtils.h',
     'ScrollbarActivity.h',
     'ScrollSnap.h',
+    'TextDrawTarget.h',
     'Visibility.h',
 ]
 
 EXPORTS.mozilla += [
     'CSSAlignUtils.h',
     'CSSOrderAwareFrameIterator.h',
     'FrameTypeList.h',
     'ReflowInput.h',
--- a/layout/generic/nsBulletFrame.cpp
+++ b/layout/generic/nsBulletFrame.cpp
@@ -514,17 +514,21 @@ BulletRenderer::CreateWebRenderCommandsF
 
   const int32_t appUnitsPerDevPixel = aItem->Frame()->PresContext()->AppUnitsPerDevPixel();
   bool dummy;
   LayerRect destRect = ViewAs<LayerPixel>(
       LayoutDeviceRect::FromAppUnits(
           aItem->GetBounds(aDisplayListBuilder, &dummy), appUnitsPerDevPixel),
       PixelCastJustification::WebRenderHasUnitResolution);
 
-  aManager->WrBridge()->PushGlyphs(aBuilder, mGlyphs, mFont, aSc, destRect, destRect);
+  for (layers::GlyphArray& glyphs : mGlyphs) {
+    aManager->WrBridge()->PushGlyphs(aBuilder, glyphs.glyphs(), mFont,
+                                     glyphs.color().value(),
+                                     aSc, destRect, destRect);
+  }
 }
 
 class nsDisplayBullet final : public nsDisplayItem {
 public:
   nsDisplayBullet(nsDisplayListBuilder* aBuilder, nsBulletFrame* aFrame)
     : nsDisplayItem(aBuilder, aFrame)
     , mDisableSubpixelAA(false)
   {
--- a/layout/generic/nsTextFrame.cpp
+++ b/layout/generic/nsTextFrame.cpp
@@ -50,16 +50,17 @@
 #include "nsIMathMLFrame.h"
 #include "nsPlaceholderFrame.h"
 #include "nsTextFrameUtils.h"
 #include "nsTextRunTransformations.h"
 #include "MathMLTextRunFactory.h"
 #include "nsUnicodeProperties.h"
 #include "nsStyleUtil.h"
 #include "nsRubyFrame.h"
+#include "TextDrawTarget.h"
 
 #include "nsTextFragment.h"
 #include "nsGkAtoms.h"
 #include "nsFrameSelection.h"
 #include "nsRange.h"
 #include "nsCSSRendering.h"
 #include "nsContentUtils.h"
 #include "nsLineBreaker.h"
@@ -77,16 +78,17 @@
 #include <limits>
 #ifdef ACCESSIBILITY
 #include "nsAccessibilityService.h"
 #endif
 
 #include "nsPrintfCString.h"
 
 #include "gfxContext.h"
+#include "mozilla/gfx/DrawTargetRecording.h"
 
 #include "mozilla/UniquePtr.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/LookAndFeel.h"
 
 #include "GeckoProfiler.h"
 
 #ifdef DEBUG
@@ -101,16 +103,18 @@
 #undef DrawText
 #endif
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::gfx;
 using namespace mozilla::layers;
 
+typedef mozilla::layout::TextDrawTarget TextDrawTarget;
+
 struct TabWidth {
   TabWidth(uint32_t aOffset, uint32_t aWidth)
     : mOffset(aOffset), mWidth(float(aWidth))
   { }
 
   uint32_t mOffset; // DOM offset relative to the current frame's offset.
   float    mWidth;  // extra space to be added at this position (in app units)
 };
@@ -4970,17 +4974,17 @@ public:
   virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
                                          const nsDisplayItemGeometry* aGeometry,
                                          nsRegion *aInvalidRegion) override;
 
   virtual void DisableComponentAlpha() override {
     mDisableSubpixelAA = true;
   }
 
-  void RenderToContext(gfxContext* aCtx, nsDisplayListBuilder* aBuilder, bool aIsRecording = false);
+  void RenderToContext(gfxContext* aCtx, TextDrawTarget* aTextDrawer, nsDisplayListBuilder* aBuilder, bool aIsRecording = false);
 
   bool CanApplyOpacity() const override
   {
     nsTextFrame* f = static_cast<nsTextFrame*>(mFrame);
     if (f->IsSelected()) {
       return false;
     }
 
@@ -5025,44 +5029,47 @@ public:
   }
 
   void GetMergedFrames(nsTArray<nsIFrame*>* aFrames) override
   {
     aFrames->AppendElements(mMergedFrames);
   }
 
   bool TryMerge(nsDisplayItem* aItem) override {
-    if (aItem->GetType() != DisplayItemType::TYPE_TEXT)
+    if (aItem->GetType() != DisplayItemType::TYPE_TEXT) {
       return false;
-    if (aItem->GetClipChain() != GetClipChain())
+    }
+
+    if (aItem->GetClipChain() != GetClipChain()) {
       return false;
+    }
 
     nsDisplayText* other = static_cast<nsDisplayText*>(aItem);
-    if (!mFont || !other->mFont || mFont != other->mFont) {
+    if (!mTextDrawer || !other->mTextDrawer) {
       return false;
     }
+
     if (mOpacity != other->mOpacity) {
       return false;
     }
 
+    if (!mTextDrawer->TryMerge(*other->mTextDrawer)) {
+      return false;
+    }
+
     mBounds.UnionRect(mBounds, other->mBounds);
     mVisibleRect.UnionRect(mVisibleRect, other->mVisibleRect);
     mMergedFrames.AppendElement(static_cast<nsTextFrame*>(other->mFrame));
     mMergedFrames.AppendElements(mozilla::Move(other->mMergedFrames));
 
-    for (GlyphArray& g : other->mGlyphs) {
-      GlyphArray* append = mGlyphs.AppendElement();
-      append->color() = g.color();
-      append->glyphs().SwapElements(g.glyphs());
-    }
     return true;
 }
 
-  RefPtr<ScaledFont> mFont;
-  nsTArray<GlyphArray> mGlyphs;
+  RefPtr<TextDrawTarget> mTextDrawer;
+
   nsTArray<nsTextFrame*> mMergedFrames;
   nsRect mBounds;
 
   float mOpacity;
   bool mDisableSubpixelAA;
 };
 
 class nsDisplayTextGeometry : public nsCharClipGeometry
@@ -5133,68 +5140,82 @@ nsDisplayText::nsDisplayText(nsDisplayLi
   MOZ_COUNT_CTOR(nsDisplayText);
   mIsFrameSelected = aIsSelected;
   mBounds = mFrame->GetVisualOverflowRectRelativeToSelf() + ToReferenceFrame();
     // Bug 748228
   mBounds.Inflate(mFrame->PresContext()->AppUnitsPerDevPixel());
 
   if (gfxPrefs::LayersAllowTextLayers() &&
       CanUseAdvancedLayer(aBuilder->GetWidgetLayerManager())) {
-    RefPtr<DrawTarget> screenTarget = gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
-    RefPtr<DrawTargetCapture> capture =
-      Factory::CreateCaptureDrawTarget(screenTarget->GetBackendType(),
-                                       IntSize(),
-                                       screenTarget->GetFormat());
-    RefPtr<gfxContext> captureCtx = gfxContext::CreateOrNull(capture);
+    mTextDrawer = new TextDrawTarget();
+    RefPtr<gfxContext> captureCtx = gfxContext::CreateOrNull(mTextDrawer);
 
     // TODO: Paint() checks mDisableSubpixelAA, we should too.
-    RenderToContext(captureCtx, aBuilder, true);
-
-    // TODO: Ideally we'd re-use captureCtx in Paint() if we couldn't build
-    // a layer here. We have to deal with the problem that the ScreenReferenceDrawTarget
-    // might not be compatible with the DT used for layer rendering.
-
-    GlyphArray* g = mGlyphs.AppendElement();
-    std::vector<Glyph> glyphs;
-    Color color;
-    if (!capture->ContainsOnlyColoredGlyphs(mFont, color, glyphs)
-        || !mFont
-        || !mFont->CanSerialize()) {
-      mFont = nullptr;
-      mGlyphs.Clear();
-    } else {
-      g->glyphs().SetLength(glyphs.size());
-      PodCopy(g->glyphs().Elements(), glyphs.data(), glyphs.size());
-      g->color() = color;
-    }
+    RenderToContext(captureCtx, mTextDrawer, aBuilder, true);
   }
 }
 
 LayerState
 nsDisplayText::GetLayerState(nsDisplayListBuilder* aBuilder,
                              LayerManager* aManager,
                              const ContainerLayerParameters& aParameters)
 {
-  if (mFont) {
+  // Basic things that all advanced backends need
+  if (!mTextDrawer ||
+      !mTextDrawer->CanSerializeFonts() ||
+      XRE_IsParentProcess()) {
+    return mozilla::LAYER_NONE;
+  }
+
+  // If we're using the webrender backend, then we're good to go!
+  if (aManager->GetBackendType() == layers::LayersBackend::LAYERS_WR) {
     return mozilla::LAYER_ACTIVE;
   }
-  MOZ_ASSERT(mMergedFrames.IsEmpty());
-  return mozilla::LAYER_NONE;
+
+  // If we're using the TextLayer backend, then we need to make sure
+  // the input is plain enough for it to handle
+
+  // Can't handle shadows, selections, or decorations
+  if (mTextDrawer->GetShadows().Length() > 0 ||
+      mTextDrawer->GetSelections().Length() > 0 ||
+      mTextDrawer->GetBeforeDecorations().Length() > 0 ||
+      mTextDrawer->GetAfterDecorations().Length() > 0) {
+    return mozilla::LAYER_NONE;
+  }
+
+  // Must only have one font (multiple colors is fine)
+  ScaledFont* font = nullptr;
+
+  for (const mozilla::layout::TextRunFragment& text : mTextDrawer->GetText()) {
+    if (!font) {
+      font = text.font;
+    }
+    if (font != text.font) {
+      return mozilla::LAYER_NONE;
+    }
+  }
+
+  // Must have an actual font (i.e. actual text)
+  if (!font) {
+    return mozilla::LAYER_NONE;
+  }
+
+  return mozilla::LAYER_ACTIVE;
 }
 
 void
 nsDisplayText::Paint(nsDisplayListBuilder* aBuilder,
                      gfxContext* aCtx) {
   AUTO_PROFILER_LABEL("nsDisplayText::Paint", GRAPHICS);
 
   MOZ_ASSERT(mMergedFrames.IsEmpty());
 
   DrawTargetAutoDisableSubpixelAntialiasing disable(aCtx->GetDrawTarget(),
                                                     mDisableSubpixelAA);
-  RenderToContext(aCtx, aBuilder);
+  RenderToContext(aCtx, nullptr, aBuilder);
 }
 
 bool
 nsDisplayText::CreateWebRenderCommands(mozilla::wr::DisplayListBuilder& aBuilder,
                                        const StackingContextHelper& aSc,
                                        nsTArray<WebRenderParentCommand>& aParentCommands,
                                        WebRenderLayerManager* aManager,
                                        nsDisplayListBuilder* aDisplayListBuilder)
@@ -5206,59 +5227,118 @@ nsDisplayText::CreateWebRenderCommands(m
     }
   }
 
   if (mBounds.IsEmpty()) {
     return true;
   }
 
   auto appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
-  LayoutDeviceRect rect = LayoutDeviceRect::FromAppUnits(
+  LayoutDeviceRect layoutBoundsRect = LayoutDeviceRect::FromAppUnits(
       mBounds, appUnitsPerDevPixel);
-  LayoutDeviceRect clipRect = rect;
+  LayoutDeviceRect layoutClipRect = layoutBoundsRect;
   if (GetClip().HasClip()) {
-    clipRect = LayoutDeviceRect::FromAppUnits(
+    layoutClipRect = LayoutDeviceRect::FromAppUnits(
                 GetClip().GetClipRect(), appUnitsPerDevPixel);
   }
-  aManager->WrBridge()->PushGlyphs(aBuilder, mGlyphs, mFont, aSc,
-                                   LayerRect::FromUnknownRect(rect.ToUnknownRect()),
-                                   LayerRect::FromUnknownRect(clipRect.ToUnknownRect()));
+
+  LayerRect boundsRect = LayerRect::FromUnknownRect(layoutBoundsRect.ToUnknownRect());
+  LayerRect clipRect = LayerRect::FromUnknownRect(layoutClipRect.ToUnknownRect());
+  wr::LayoutRect wrClipRect = aSc.ToRelativeLayoutRect(clipRect); // wr::ToLayoutRect(clipRect);
+  wr::LayoutRect wrBoundsRect = aSc.ToRelativeLayoutRect(boundsRect); //wr::ToLayoutRect(boundsRect);
+
+  // Drawing order: selections, shadows,
+  //                underline, overline, [grouped in one array]
+  //                text, emphasisText,  [grouped in one array]
+  //                lineThrough
+
+  for (const mozilla::layout::SelectionFragment& selection:
+       mTextDrawer->GetSelections()) {
+    aBuilder.PushRect(selection.rect, wrClipRect, selection.color);
+  }
+
+  // WR takes the shadows in CSS-order (reverse of rendering order),
+  // because the drawing of a shadow actually occurs when it's popped.
+  for (const wr::TextShadow& shadow : mTextDrawer->GetShadows()) {
+    aBuilder.PushTextShadow(wrBoundsRect, wrClipRect, shadow);
+  }
+
+  for (const wr::Line& decoration: mTextDrawer->GetBeforeDecorations()) {
+    aBuilder.PushLine(wrClipRect, decoration);
+  }
+
+  for (const mozilla::layout::TextRunFragment& text: mTextDrawer->GetText()) {
+    aManager->WrBridge()->PushGlyphs(aBuilder, text.glyphs, text.font,
+                                     text.color, aSc, boundsRect, clipRect);
+  }
+
+  for (const wr::Line& decoration: mTextDrawer->GetAfterDecorations()) {
+    aBuilder.PushLine(wrClipRect, decoration);
+  }
+
+  for (size_t i = 0; i < mTextDrawer->GetShadows().Length(); ++i) {
+    aBuilder.PopTextShadow();
+  }
 
   return true;
 }
 
 already_AddRefed<layers::Layer>
 nsDisplayText::BuildLayer(nsDisplayListBuilder* aBuilder,
                           LayerManager* aManager,
                           const ContainerLayerParameters& aContainerParameters)
 {
+  // If we're using webrender, we want layerless rendering, so emit a dummy.
+  // See CreateWebRenderCommands for actual drawing code.
+  if (aManager->GetBackendType() == layers::LayersBackend::LAYERS_WR) {
+    return BuildDisplayItemLayer(aBuilder, aManager, aContainerParameters);
+  }
+
   // We should have all the glyphs recorded now, build
   // the TextLayer.
   RefPtr<layers::TextLayer> layer = static_cast<layers::TextLayer*>
     (aManager->GetLayerBuilder()->GetLeafLayerFor(aBuilder, this));
   if (!layer) {
     layer = aManager->CreateTextLayer();
   }
 
-  layer->SetGlyphs(Move(mGlyphs));
-  layer->SetScaledFont(mFont);
+  // GetLayerState has guaranteed to us that we have exactly one font
+  // so this will be overwritten by the time we use it.
+  ScaledFont* font = nullptr;
+
+  nsTArray<GlyphArray> allGlyphs;
+  allGlyphs.SetCapacity(mTextDrawer->GetText().Length());
+  for (const mozilla::layout::TextRunFragment& text : mTextDrawer->GetText()) {
+    if (!font) {
+      font = text.font;
+    }
+
+    GlyphArray* glyphs = allGlyphs.AppendElement();
+    glyphs->glyphs() = text.glyphs;
+    glyphs->color() = text.color;
+  }
+
+  MOZ_ASSERT(font);
+
+  layer->SetGlyphs(Move(allGlyphs));
+  layer->SetScaledFont(font);
 
   auto A2D = mFrame->PresContext()->AppUnitsPerDevPixel();
   bool dummy;
   const LayoutDeviceIntRect destBounds =
           LayoutDeviceIntRect::FromAppUnitsToOutside(GetBounds(aBuilder, &dummy), A2D);
   layer->SetBounds(IntRect(destBounds.x, destBounds.y, destBounds.width, destBounds.height));
 
   layer->SetBaseTransform(gfx::Matrix4x4::Translation(aContainerParameters.mOffset.x,
                                                       aContainerParameters.mOffset.y, 0));
   return layer.forget();
 }
 
 void
-nsDisplayText::RenderToContext(gfxContext* aCtx, nsDisplayListBuilder* aBuilder, bool aIsRecording)
+nsDisplayText::RenderToContext(gfxContext* aCtx, TextDrawTarget* aTextDrawer, nsDisplayListBuilder* aBuilder, bool aIsRecording)
 {
   nsTextFrame* f = static_cast<nsTextFrame*>(mFrame);
 
   // Add 1 pixel of dirty area around mVisibleRect to allow us to paint
   // antialiased pixels beyond the measured text extents.
   // This is temporary until we do this in the actual calculation of text extents.
   auto A2D = mFrame->PresContext()->AppUnitsPerDevPixel();
   LayoutDeviceRect extraVisible =
@@ -5296,16 +5376,17 @@ nsDisplayText::RenderToContext(gfxContex
       gfxMatrix mat = aCtx->CurrentMatrix()
         .PreTranslate(pt).PreScale(scaleFactor, 1.0).PreTranslate(-pt);
       aCtx->SetMatrix (mat);
     }
   }
   nsTextFrame::PaintTextParams params(aCtx);
   params.framePt = gfxPoint(framePt.x, framePt.y);
   params.dirtyRect = extraVisible;
+  params.textDrawer = aTextDrawer;
 
   if (aBuilder->IsForGenerateGlyphMask()) {
     MOZ_ASSERT(!aBuilder->IsForPaintingSelectionBG());
     params.state = nsTextFrame::PaintTextParams::GenerateTextMask;
   } else if (aBuilder->IsForPaintingSelectionBG()) {
     params.state = nsTextFrame::PaintTextParams::PaintTextBGColor;
   } else {
     params.state = nsTextFrame::PaintTextParams::PaintText;
@@ -5987,16 +6068,17 @@ nsTextFrame::ComputeSelectionUnderlineHe
 enum class DecorationType
 {
   Normal, Selection
 };
 struct nsTextFrame::PaintDecorationLineParams
   : nsCSSRendering::DecorationRectParams
 {
   gfxContext* context = nullptr;
+  TextDrawTarget* textDrawer = nullptr;
   LayoutDeviceRect dirtyRect;
   Point pt;
   const nscolor* overrideColor = nullptr;
   nscolor color = NS_RGBA(0, 0, 0, 0);
   gfxFloat icoordInFrame = 0.0f;
   DecorationType decorationType = DecorationType::Normal;
   DrawPathCallbacks* callbacks = nullptr;
 };
@@ -6005,16 +6087,17 @@ void
 nsTextFrame::PaintDecorationLine(const PaintDecorationLineParams& aParams)
 {
   nsCSSRendering::PaintDecorationLineParams params;
   static_cast<nsCSSRendering::DecorationRectParams&>(params) = aParams;
   params.dirtyRect = aParams.dirtyRect.ToUnknownRect();
   params.pt = aParams.pt;
   params.color = aParams.overrideColor ? *aParams.overrideColor : aParams.color;
   params.icoordInFrame = Float(aParams.icoordInFrame);
+  params.textDrawer = aParams.textDrawer;
   if (aParams.callbacks) {
     Rect path = nsCSSRendering::DecorationLineToPath(params);
     if (aParams.decorationType == DecorationType::Normal) {
       aParams.callbacks->PaintDecorationLine(path, params.color);
     } else {
       aParams.callbacks->PaintSelectionDecorationLine(path, params.color);
     }
   } else {
@@ -6420,25 +6503,41 @@ nsTextFrame::PaintOneShadow(const PaintS
   if (aShadowDetails->mHasColor) {
     shadowColor = aShadowDetails->mColor;
     decorationOverrideColor = &shadowColor;
   } else {
     shadowColor = aParams.foregroundColor;
     decorationOverrideColor = nullptr;
   }
 
+  if (aParams.textDrawer) {
+    wr::TextShadow wrShadow;
+
+    wrShadow.offset = {
+      PresContext()->AppUnitsToFloatDevPixels(aShadowDetails->mXOffset),
+      PresContext()->AppUnitsToFloatDevPixels(aShadowDetails->mYOffset)
+    };
+
+    wrShadow.blur_radius = PresContext()->AppUnitsToFloatDevPixels(aShadowDetails->mRadius);
+    wrShadow.color = wr::ToColorF(ToDeviceColor(shadowColor));
+
+    aParams.textDrawer->AppendShadow(wrShadow);
+    return;
+  }
+
   aParams.context->Save();
   aParams.context->SetColor(Color::FromABGR(shadowColor));
 
   // Draw the text onto our alpha-only surface to capture the alpha values.
   // Remember that the box blur context has a device offset on it, so we don't need to
   // translate any coordinates to fit on the surface.
   gfxFloat advanceWidth;
   nsTextPaintStyle textPaintStyle(this);
   DrawTextParams params(shadowContext);
+  params.textDrawer = nullptr; // Don't record anything that happens here
   params.advanceWidth = &advanceWidth;
   params.dirtyRect = aParams.dirtyRect;
   params.framePt = aParams.framePt + shadowOffset;
   params.provider = aParams.provider;
   params.textStyle = &textPaintStyle;
   params.textColor =
     aParams.context == shadowContext ? shadowColor : NS_RGB(0, 0, 0);
   params.clipEdges = aParams.clipEdges;
@@ -6537,31 +6636,40 @@ nsTextFrame::PaintTextWithSelectionColor
         gfxFloat offs = iOffset - (mTextRun->IsInlineReversed() ? advance : 0);
         if (vertical) {
           bgRect = nsRect(aParams.framePt.x, aParams.framePt.y + offs,
                           GetSize().width, advance);
         } else {
           bgRect = nsRect(aParams.framePt.x + offs, aParams.framePt.y,
                           advance, GetSize().height);
         }
-        PaintSelectionBackground(
-          *aParams.context->GetDrawTarget(), background, aParams.dirtyRect,
-          LayoutDeviceRect::FromAppUnits(bgRect, appUnitsPerDevPixel),
-          aParams.callbacks);
+
+        LayoutDeviceRect selectionRect =
+          LayoutDeviceRect::FromAppUnits(bgRect, appUnitsPerDevPixel);
+
+        if (aParams.textDrawer) {
+          aParams.textDrawer->AppendSelection(selectionRect,
+                                              ToDeviceColor(background));
+        } else {
+          PaintSelectionBackground(
+            *aParams.context->GetDrawTarget(), background, aParams.dirtyRect,
+            selectionRect, aParams.callbacks);
+        }
       }
       iterator.UpdateWithAdvance(advance);
     }
   }
 
   if (aParams.IsPaintBGColor()) {
     return true;
   }
 
   gfxFloat advance;
   DrawTextParams params(aParams.context);
+  params.textDrawer = aParams.textDrawer;
   params.dirtyRect = aParams.dirtyRect;
   params.framePt = aParams.framePt;
   params.provider = aParams.provider;
   params.textStyle = aParams.textPaintStyle;
   params.clipEdges = &aClipEdges;
   params.advanceWidth = &advance;
   params.callbacks = aParams.callbacks;
 
@@ -7157,16 +7265,17 @@ nsTextFrame::PaintText(const PaintTextPa
     shadowParams.provider = &provider;
     shadowParams.foregroundColor = foregroundColor;
     shadowParams.clipEdges = &clipEdges;
     PaintShadows(textStyle->mTextShadow, shadowParams);
   }
 
   gfxFloat advanceWidth;
   DrawTextParams params(aParams.context);
+  params.textDrawer = aParams.textDrawer;
   params.dirtyRect = aParams.dirtyRect;
   params.framePt = aParams.framePt;
   params.provider = &provider;
   params.advanceWidth = &advanceWidth;
   params.textStyle = &textPaintStyle;
   params.textColor = foregroundColor;
   params.textStrokeColor = textStrokeColor;
   params.textStrokeWidth = textPaintStyle.GetWebkitTextStrokeWidth();
@@ -7190,17 +7299,16 @@ DrawTextRun(const gfxTextRun* aTextRun,
   params.callbacks = aParams.callbacks;
   if (aParams.callbacks) {
     aParams.callbacks->NotifyBeforeText(aParams.textColor);
     params.drawMode = DrawMode::GLYPH_PATH;
     aTextRun->Draw(aRange, aTextBaselinePt, params);
     aParams.callbacks->NotifyAfterText();
   } else {
     if (NS_GET_A(aParams.textColor) != 0) {
-      // Default drawMode is DrawMode::GLYPH_FILL
       aParams.context->SetColor(Color::FromABGR(aParams.textColor));
     } else {
       params.drawMode = DrawMode::GLYPH_STROKE;
     }
 
     if (NS_GET_A(aParams.textStrokeColor) != 0 &&
         aParams.textStrokeWidth != 0.0f) {
       StrokeOptions strokeOpts;
@@ -7215,16 +7323,21 @@ DrawTextRun(const gfxTextRun* aTextRun,
   }
 }
 
 void
 nsTextFrame::DrawTextRun(Range aRange, const gfxPoint& aTextBaselinePt,
                          const DrawTextRunParams& aParams)
 {
   MOZ_ASSERT(aParams.advanceWidth, "Must provide advanceWidth");
+
+  if (aParams.textDrawer) {
+    aParams.textDrawer->StartDrawing(TextDrawTarget::Phase::eGlyphs);
+  }
+
   ::DrawTextRun(mTextRun, aTextBaselinePt, aRange, aParams);
 
   if (aParams.drawSoftHyphen) {
     // Don't use ctx as the context, because we need a reference context here,
     // ctx may be transformed.
     RefPtr<gfxTextRun> hyphenTextRun =
       GetHyphenTextRun(mTextRun, nullptr, this);
     if (hyphenTextRun) {
@@ -7299,16 +7412,17 @@ nsTextFrame::DrawTextRunAndDecorations(R
     // so we will multiply the values from metrics by this factor.
     gfxFloat decorationOffsetDir = mTextRun->IsSidewaysLeft() ? -1.0 : 1.0;
 
     PaintDecorationLineParams params;
     params.context = aParams.context;
     params.dirtyRect = aParams.dirtyRect;
     params.overrideColor = aParams.decorationOverrideColor;
     params.callbacks = aParams.callbacks;
+    params.textDrawer = aParams.textDrawer;
     // pt is the physical point where the decoration is to be drawn,
     // relative to the frame; one of its coordinates will be updated below.
     params.pt = Point(x / app, y / app);
     Float& bCoord = verticalDec ? params.pt.x : params.pt.y;
     params.lineSize = Size(measure / app, 0);
     params.ascent = ascent;
     params.vertical = verticalDec;
 
@@ -7346,22 +7460,30 @@ nsTextFrame::DrawTextRunAndDecorations(R
 
       params.color = dec.mColor;
       params.offset = decorationOffsetDir * metrics.*lineOffset;
       params.style = dec.mStyle;
       PaintDecorationLine(params);
     };
 
     // Underlines
+    if (aParams.textDrawer && aDecorations.mUnderlines.Length() > 0) {
+      aParams.textDrawer->StartDrawing(TextDrawTarget::Phase::eUnderline);
+    }
+    //
     params.decoration = NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE;
     for (const LineDecoration& dec : Reversed(aDecorations.mUnderlines)) {
       paintDecorationLine(dec, &Metrics::underlineSize,
                           &Metrics::underlineOffset);
     }
+
     // Overlines
+    if (aParams.textDrawer && aDecorations.mOverlines.Length() > 0) {
+      aParams.textDrawer->StartDrawing(TextDrawTarget::Phase::eOverline);
+    }
     params.decoration = NS_STYLE_TEXT_DECORATION_LINE_OVERLINE;
     for (const LineDecoration& dec : Reversed(aDecorations.mOverlines)) {
       paintDecorationLine(dec, &Metrics::underlineSize, &Metrics::maxAscent);
     }
 
     {
       gfxContextMatrixAutoSaveRestore unscaledRestorer;
       if (scaledRestorer.HasMatrix()) {
@@ -7370,21 +7492,27 @@ nsTextFrame::DrawTextRunAndDecorations(R
       }
 
       // CSS 2.1 mandates that text be painted after over/underlines,
       // and *then* line-throughs
       DrawTextRun(aRange, aTextBaselinePt, aParams);
     }
 
     // Emphasis marks
+    if (aParams.textDrawer) {
+      aParams.textDrawer->StartDrawing(TextDrawTarget::Phase::eEmphasisMarks);
+    }
     DrawEmphasisMarks(aParams.context, wm,
                       aTextBaselinePt, aParams.framePt, aRange,
                       aParams.decorationOverrideColor, aParams.provider);
 
     // Line-throughs
+    if (aParams.textDrawer && aDecorations.mStrikes.Length() > 0) {
+      aParams.textDrawer->StartDrawing(TextDrawTarget::Phase::eLineThrough);
+    }
     params.decoration = NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH;
     for (const LineDecoration& dec : Reversed(aDecorations.mStrikes)) {
       paintDecorationLine(dec, &Metrics::strikeoutSize,
                           &Metrics::strikeoutOffset);
     }
 }
 
 void
--- a/layout/generic/nsTextFrame.h
+++ b/layout/generic/nsTextFrame.h
@@ -15,16 +15,17 @@
 #include "nsGenericDOMDataNode.h"
 #include "nsSplittableFrame.h"
 #include "nsLineBox.h"
 #include "gfxSkipChars.h"
 #include "gfxTextRun.h"
 #include "nsDisplayList.h"
 #include "JustificationUtils.h"
 #include "RubyUtils.h"
+#include "TextDrawTarget.h"
 
 // Undo the windows.h damage
 #if defined(XP_WIN) && defined(DrawText)
 #undef DrawText
 #endif
 
 class nsTextPaintStyle;
 class PropertyProvider;
@@ -43,16 +44,17 @@ class nsTextFrame : public nsFrame
   typedef mozilla::LayoutDeviceRect LayoutDeviceRect;
   typedef mozilla::RawSelectionType RawSelectionType;
   typedef mozilla::SelectionType SelectionType;
   typedef mozilla::TextRangeStyle TextRangeStyle;
   typedef mozilla::gfx::DrawTarget DrawTarget;
   typedef mozilla::gfx::Point Point;
   typedef mozilla::gfx::Rect Rect;
   typedef mozilla::gfx::Size Size;
+  typedef mozilla::layout::TextDrawTarget TextDrawTarget;
   typedef gfxTextRun::Range Range;
 
 public:
   explicit nsTextFrame(nsStyleContext* aContext, ClassID aID = kClassID)
     : nsFrame(aContext, aID)
     , mNextContinuation(nullptr)
     , mContentOffset(0)
     , mContentLengthHint(0)
@@ -436,33 +438,34 @@ public:
      * has been emitted to the gfxContext.
      */
     virtual void NotifySelectionDecorationLinePathEmitted() {}
   };
 
   struct PaintTextParams
   {
     gfxContext* context;
+    TextDrawTarget* textDrawer;
     gfxPoint framePt;
     LayoutDeviceRect dirtyRect;
     mozilla::SVGContextPaint* contextPaint = nullptr;
     DrawPathCallbacks* callbacks = nullptr;
     enum
     {
       PaintText,        // Normal text painting.
       PaintTextBGColor, // Only paint background color of the selected text
                         // range in this state.
       GenerateTextMask  // To generate a mask from a text frame. Should
                         // only paint text itself with opaque color.
                         // Text shadow, text selection color and text
                         // decoration are all discarded in this state.
     };
     uint8_t state = PaintText;
     explicit PaintTextParams(gfxContext* aContext)
-      : context(aContext)
+      : context(aContext), textDrawer(nullptr)
     {
     }
 
     bool IsPaintText() const { return state == PaintText; }
     bool IsGenerateTextMask() const { return state == GenerateTextMask; }
     bool IsPaintBGColor() const { return state == PaintTextBGColor; }
   };
 
@@ -475,26 +478,27 @@ public:
     explicit PaintTextSelectionParams(const PaintTextParams& aParams)
       : PaintTextParams(aParams)
     {}
   };
 
   struct DrawTextRunParams
   {
     gfxContext* context;
+    TextDrawTarget* textDrawer;
     PropertyProvider* provider = nullptr;
     gfxFloat* advanceWidth = nullptr;
     mozilla::SVGContextPaint* contextPaint = nullptr;
     DrawPathCallbacks* callbacks = nullptr;
     nscolor textColor = NS_RGBA(0, 0, 0, 0);
     nscolor textStrokeColor = NS_RGBA(0, 0, 0, 0);
     float textStrokeWidth = 0.0f;
     bool drawSoftHyphen = false;
     explicit DrawTextRunParams(gfxContext* aContext)
-      : context(aContext)
+      : context(aContext), textDrawer(nullptr)
     {}
   };
 
   struct DrawTextParams : DrawTextRunParams
   {
     gfxPoint framePt;
     LayoutDeviceRect dirtyRect;
     const nsTextPaintStyle* textStyle = nullptr;
@@ -701,24 +705,26 @@ protected:
 
   struct PaintShadowParams
   {
     gfxTextRun::Range range;
     LayoutDeviceRect dirtyRect;
     gfxPoint framePt;
     gfxPoint textBaselinePt;
     gfxContext* context;
+    TextDrawTarget* textDrawer;
     nscolor foregroundColor = NS_RGBA(0, 0, 0, 0);
     const nsCharClipDisplayItem::ClipEdges* clipEdges = nullptr;
     PropertyProvider* provider = nullptr;
     nscoord leftSideOffset = 0;
     explicit PaintShadowParams(const PaintTextParams& aParams)
       : dirtyRect(aParams.dirtyRect)
       , framePt(aParams.framePt)
       , context(aParams.context)
+      , textDrawer(aParams.textDrawer)
     {
     }
   };
 
   void PaintOneShadow(const PaintShadowParams& aParams,
                       nsCSSShadowItem* aShadowDetails,
                       gfxRect& aBoundingBox,
                       uint32_t aBlurFlags);
--- a/layout/painting/nsCSSRendering.cpp
+++ b/layout/painting/nsCSSRendering.cpp
@@ -56,16 +56,17 @@
 #include "mozilla/Telemetry.h"
 #include "gfxUtils.h"
 #include "gfxGradientCache.h"
 #include "nsInlineFrame.h"
 #include "nsRubyTextContainerFrame.h"
 #include <algorithm>
 #include "SVGImageContext.h"
 #include "mozilla/layers/WebRenderDisplayItemLayer.h"
+#include "TextDrawTarget.h"
 
 using namespace mozilla;
 using namespace mozilla::css;
 using namespace mozilla::gfx;
 using namespace mozilla::image;
 using mozilla::CSSSizeOrRatio;
 
 static int gFrameTreeLockCount = 0;
@@ -3798,17 +3799,18 @@ nsCSSRendering::PaintDecorationLine(nsIF
       aParams.decoration != NS_STYLE_TEXT_DECORATION_LINE_OVERLINE &&
       aParams.decoration != NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH) {
     NS_ERROR("Invalid decoration value!");
     return;
   }
 
   Float lineThickness = std::max(NS_round(aParams.lineSize.height), 1.0);
 
-  ColorPattern color(ToDeviceColor(aParams.color));
+  Color color = ToDeviceColor(aParams.color);
+  ColorPattern colorPat(color);
   StrokeOptions strokeOptions(lineThickness);
   DrawOptions drawOptions;
 
   Float dash[2];
 
   AutoPopClips autoPopClips(&aDrawTarget);
 
   switch (aParams.style) {
@@ -3876,17 +3878,22 @@ nsCSSRendering::PaintDecorationLine(nsIF
   }
 
   switch (aParams.style) {
     case NS_STYLE_TEXT_DECORATION_STYLE_SOLID:
     case NS_STYLE_TEXT_DECORATION_STYLE_DOTTED:
     case NS_STYLE_TEXT_DECORATION_STYLE_DASHED: {
       Point p1 = rect.TopLeft();
       Point p2 = aParams.vertical ? rect.BottomLeft() : rect.TopRight();
-      aDrawTarget.StrokeLine(p1, p2, color, strokeOptions, drawOptions);
+      if (aParams.textDrawer) {
+        aParams.textDrawer->AppendDecoration(
+          p1, p2, lineThickness, aParams.vertical, color, aParams.style);
+      } else {
+        aDrawTarget.StrokeLine(p1, p2, colorPat, strokeOptions, drawOptions);
+      }
       return;
     }
     case NS_STYLE_TEXT_DECORATION_STYLE_DOUBLE: {
       /**
        *  We are drawing double line as:
        *
        * +-------------------------------------------+
        * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^
@@ -3894,29 +3901,39 @@ nsCSSRendering::PaintDecorationLine(nsIF
        * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v
        * |                                           |
        * |                                           |
        * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^
        * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineThickness
        * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v
        * +-------------------------------------------+
        */
-      Point p1 = rect.TopLeft();
-      Point p2 = aParams.vertical ? rect.BottomLeft() : rect.TopRight();
-      aDrawTarget.StrokeLine(p1, p2, color, strokeOptions, drawOptions);
+      Point p1a = rect.TopLeft();
+      Point p2a = aParams.vertical ? rect.BottomLeft() : rect.TopRight();
 
       if (aParams.vertical) {
         rect.width -= lineThickness;
       } else {
         rect.height -= lineThickness;
       }
 
-      p1 = aParams.vertical ? rect.TopRight() : rect.BottomLeft();
-      p2 = rect.BottomRight();
-      aDrawTarget.StrokeLine(p1, p2, color, strokeOptions, drawOptions);
+      Point p1b = aParams.vertical ? rect.TopRight() : rect.BottomLeft();
+      Point p2b = rect.BottomRight();
+
+      if (aParams.textDrawer) {
+        aParams.textDrawer->AppendDecoration(
+          p1a, p2a, lineThickness, aParams.vertical, color,
+          NS_STYLE_TEXT_DECORATION_STYLE_SOLID);
+        aParams.textDrawer->AppendDecoration(
+          p1b, p2b, lineThickness, aParams.vertical, color,
+          NS_STYLE_TEXT_DECORATION_STYLE_SOLID);
+      } else {
+        aDrawTarget.StrokeLine(p1a, p2a, colorPat, strokeOptions, drawOptions);
+        aDrawTarget.StrokeLine(p1b, p2b, colorPat, strokeOptions, drawOptions);
+      }
       return;
     }
     case NS_STYLE_TEXT_DECORATION_STYLE_WAVY: {
       /**
        *  We are drawing wavy line as:
        *
        *  P: Path, X: Painted pixel
        *
@@ -3942,16 +3959,18 @@ nsCSSRendering::PaintDecorationLine(nsIF
        *  5. Goes up to top of the area at 45 degrees.
        *  6. Slides to right horizontaly.
        *  7. Repeat from 2 until reached to right-most edge of the area.
        *
        * In the vertical case, swap horizontal and vertical coordinates and
        * directions in the above description.
        */
 
+      // TODO(gankro)
+
       Float& rectICoord = aParams.vertical ? rect.y : rect.x;
       Float& rectISize = aParams.vertical ? rect.height : rect.width;
       const Float rectBSize = aParams.vertical ? rect.width : rect.height;
 
       const Float adv = rectBSize - lineThickness;
       const Float flatLengthAtVertex =
         std::max((lineThickness - 1.0) * 2.0, 1.0);
 
@@ -3967,16 +3986,27 @@ nsCSSRendering::PaintDecorationLine(nsIF
                                                      : aParams.dirtyRect.x;
       int32_t skipCycles = floor((dirtyRectICoord - rectICoord) / cycleLength);
       if (skipCycles > 0) {
         rectICoord += skipCycles * cycleLength;
         rectISize -= skipCycles * cycleLength;
       }
 
       rectICoord += lineThickness / 2.0;
+
+      if (aParams.textDrawer) {
+        Point p1 = rect.TopLeft();
+        Point p2 = aParams.vertical ? rect.BottomLeft() : rect.TopRight();
+
+        aParams.textDrawer->AppendDecoration(
+          p1, p2, adv, aParams.vertical, color,
+          NS_STYLE_TEXT_DECORATION_STYLE_WAVY);
+        return;
+      }
+
       Point pt(rect.TopLeft());
       Float& ptICoord = aParams.vertical ? pt.y : pt.x;
       Float& ptBCoord = aParams.vertical ? pt.x : pt.y;
       if (aParams.vertical) {
         ptBCoord += adv + lineThickness / 2.0;
       }
       Float iCoordLimit = ptICoord + rectISize + lineThickness;
 
@@ -4001,33 +4031,33 @@ nsCSSRendering::PaintDecorationLine(nsIF
       // it. So the sense of this flag is effectively inverted.
       bool goDown = aParams.vertical ? false : true;
       uint32_t iter = 0;
       while (ptICoord < iCoordLimit) {
         if (++iter > 1000) {
           // stroke the current path and start again, to avoid pathological
           // behavior in cairo with huge numbers of path segments
           path = builder->Finish();
-          aDrawTarget.Stroke(path, color, strokeOptions, drawOptions);
+          aDrawTarget.Stroke(path, colorPat, strokeOptions, drawOptions);
           builder = aDrawTarget.CreatePathBuilder();
           builder->MoveTo(pt);
           iter = 0;
         }
         ptICoord += adv;
         ptBCoord += goDown ? adv : -adv;
 
         builder->LineTo(pt); // 3 and 5
 
         ptICoord += flatLengthAtVertex;
         builder->LineTo(pt); // 4 and 6
 
         goDown = !goDown;
       }
       path = builder->Finish();
-      aDrawTarget.Stroke(path, color, strokeOptions, drawOptions);
+      aDrawTarget.Stroke(path, colorPat, strokeOptions, drawOptions);
       return;
     }
     default:
       NS_ERROR("Invalid style value!");
   }
 }
 
 Rect
--- a/layout/painting/nsCSSRendering.h
+++ b/layout/painting/nsCSSRendering.h
@@ -14,16 +14,17 @@
 #include "mozilla/gfx/PathHelpers.h"
 #include "mozilla/gfx/Rect.h"
 #include "mozilla/TypedEnumBits.h"
 #include "nsLayoutUtils.h"
 #include "nsStyleStruct.h"
 #include "nsIFrame.h"
 #include "nsImageRenderer.h"
 #include "nsCSSRenderingBorders.h"
+#include "TextDrawTarget.h"
 
 class gfxContext;
 class nsStyleContext;
 class nsPresContext;
 
 namespace mozilla {
 
 namespace gfx {
@@ -583,27 +584,29 @@ struct nsCSSRendering {
     // NS_STYLE_TEXT_DECORATION_LINE_OVERLINE or
     // NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH.
     uint8_t decoration = NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE;
     // The style of the decoration line such as
     // NS_STYLE_TEXT_DECORATION_STYLE_*.
     uint8_t style = NS_STYLE_TEXT_DECORATION_STYLE_NONE;
     bool vertical = false;
   };
+
   struct PaintDecorationLineParams : DecorationRectParams
   {
     // No need to paint outside this rect.
     Rect dirtyRect;
     // The top/left edge of the text.
     Point pt;
     // The color of the decoration line.
     nscolor color = NS_RGBA(0, 0, 0, 0);
     // The distance between the left edge of the given frame and the
     // position of the text as positioned without offset of the shadow.
     Float icoordInFrame = 0.0f;
+    mozilla::layout::TextDrawTarget* textDrawer = nullptr;
   };
 
   /**
    * Function for painting the decoration lines for the text.
    *
    *   input:
    *     @param aFrame            the frame which needs the decoration line
    *     @param aGfxContext