Bug 759568 - Part 2. Render background-clip:text; r=jfkthame
☠☠ backed out by d73644ea7892 ☠ ☠
authorCJKu <cku@mozilla.com>
Tue, 12 Apr 2016 21:58:22 +0800
changeset 316596 e64951d74d2a20e3f7abd074699e6d0cbd6ee515
parent 316595 7ec8bfb9bbd455dae46cfe363de70afa3d2ddf24
child 316597 21551ed8f1e1dafc9a09710afd72650de7fef3f4
push id9480
push userjlund@mozilla.com
push dateMon, 25 Apr 2016 17:12:58 +0000
treeherdermozilla-aurora@0d6a91c76a9e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjfkthame
bugs759568
milestone48.0a1
Bug 759568 - Part 2. Render background-clip:text; r=jfkthame MozReview-Commit-ID: HQHBe7OioWs
layout/base/nsCSSRendering.cpp
layout/base/nsDisplayList.cpp
layout/base/nsDisplayList.h
layout/generic/nsFrame.cpp
layout/generic/nsTextFrame.cpp
layout/generic/nsTextFrame.h
--- a/layout/base/nsCSSRendering.cpp
+++ b/layout/base/nsCSSRendering.cpp
@@ -1809,17 +1809,18 @@ nsCSSRendering::GetImageLayerClip(const 
       aClipState->mAdditionalBGClipArea.Deflate(padding);
     }
 
     // Also clip at a non-scrolling, rounded-corner 'padding-box',
     // same as the scrolled content because of the 'overflow' property.
     backgroundClip = NS_STYLE_IMAGELAYER_CLIP_PADDING;
   }
 
-  if (backgroundClip != NS_STYLE_IMAGELAYER_CLIP_BORDER) {
+  if (backgroundClip != NS_STYLE_IMAGELAYER_CLIP_BORDER &&
+      backgroundClip != NS_STYLE_IMAGELAYER_CLIP_TEXT) {
     nsMargin border = aForFrame->GetUsedBorder();
     if (backgroundClip == NS_STYLE_IMAGELAYER_CLIP_MOZ_ALMOST_PADDING) {
       // Reduce |border| by 1px (device pixels) on all sides, if
       // possible, so that we don't get antialiasing seams between the
       // background and border.
       border.top = std::max(0, border.top - aAppUnitsPerPixel);
       border.right = std::max(0, border.right - aAppUnitsPerPixel);
       border.bottom = std::max(0, border.bottom - aAppUnitsPerPixel);
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -486,16 +486,56 @@ AddAnimationsForProperty(nsIFrame* aFram
       continue;
     }
 
     AddAnimationForProperty(aFrame, *property, anim, aLayer, aData, aPending);
     effect->SetIsRunningOnCompositor(aProperty, true);
   }
 }
 
+static void
+ClipBackgroundByText(nsIFrame* aFrame, nsRenderingContext* aContext,
+                     const gfxRect& aFillRect)
+{
+  // The main function of enabling background-clip:text property value.
+  // When a nsDisplayBackgroundImage detects "text" bg-clip style, it will call
+  // this function to
+  // 1. Ask every text frame objects in aFrame puts glyph paths into aContext.
+  // 2. Then, clip aContext.
+  //
+  // Then, nsDisplayBackgroundImage paints bg-images into this clipped region,
+  // so we get images embedded in text shape!
+
+  nsDisplayListBuilder builder(aFrame, nsDisplayListBuilder::GENERATE_GLYPH, false);
+
+  builder.EnterPresShell(aFrame);
+  nsDisplayList list;
+  aFrame->BuildDisplayListForStackingContext(&builder,
+                                      nsRect(nsPoint(0, 0), aFrame->GetSize()),
+                                      &list);
+  builder.LeavePresShell(aFrame);
+
+#ifdef DEBUG
+  for (nsDisplayItem* i = list.GetBottom(); i; i = i->GetAbove()) {
+    MOZ_ASSERT(nsDisplayItem::TYPE_TEXT == i->GetType());
+  }
+#endif
+
+  gfxContext* ctx = aContext->ThebesContext();
+  gfxContextMatrixAutoSaveRestore save(ctx);
+  ctx->SetMatrix(ctx->CurrentMatrix().Translate(aFillRect.TopLeft()));
+  ctx->NewPath();
+
+  RefPtr<LayerManager> layerManager =
+    list.PaintRoot(&builder, aContext, nsDisplayList::PAINT_DEFAULT);
+
+  ctx->Clip();
+  list.DeleteAll();
+}
+
 /* static */ void
 nsDisplayListBuilder::AddAnimationsAndTransitionsToLayer(Layer* aLayer,
                                                          nsDisplayListBuilder* aBuilder,
                                                          nsDisplayItem* aItem,
                                                          nsIFrame* aFrame,
                                                          nsCSSProperty aProperty)
 {
   MOZ_ASSERT(nsCSSProps::PropHasFlags(aProperty,
@@ -2779,16 +2819,17 @@ nsDisplayBackgroundImage::GetInsideClipR
 
   nsRect clipRect;
   if (frame->GetType() == nsGkAtoms::canvasFrame) {
     nsCanvasFrame* canvasFrame = static_cast<nsCanvasFrame*>(frame);
     clipRect = canvasFrame->CanvasArea() + aItem->ToReferenceFrame();
   } else {
     switch (aClip) {
     case NS_STYLE_IMAGELAYER_CLIP_BORDER:
+    case NS_STYLE_IMAGELAYER_CLIP_TEXT:
       clipRect = nsRect(aItem->ToReferenceFrame(), frame->GetSize());
       break;
     case NS_STYLE_IMAGELAYER_CLIP_PADDING:
       clipRect = frame->GetPaddingRect() - frame->GetPosition() + aItem->ToReferenceFrame();
       break;
     case NS_STYLE_IMAGELAYER_CLIP_CONTENT:
       clipRect = frame->GetContentRectRelativeToSelf() + aItem->ToReferenceFrame();
       break;
@@ -2816,17 +2857,18 @@ nsDisplayBackgroundImage::GetOpaqueRegio
   // this could easily lead to O(N^2) behavior inside InlineBackgroundData,
   // which expects frames to be sent to it in content order, not reverse
   // content order which we'll produce here.
   // Of course, if there's only one frame in the flow, it doesn't matter.
   if (mFrame->StyleBorder()->mBoxDecorationBreak ==
         NS_STYLE_BOX_DECORATION_BREAK_CLONE ||
       (!mFrame->GetPrevContinuation() && !mFrame->GetNextContinuation())) {
     const nsStyleImageLayers::Layer& layer = mBackgroundStyle->mImage.mLayers[mLayer];
-    if (layer.mImage.IsOpaque() && layer.mBlendMode == NS_STYLE_BLEND_NORMAL) {
+    if (layer.mImage.IsOpaque() && layer.mBlendMode == NS_STYLE_BLEND_NORMAL &&
+        layer.mClip != NS_STYLE_IMAGELAYER_CLIP_TEXT) {
       result = GetInsideClipRegion(this, layer.mClip, mBounds);
     }
   }
 
   return result;
 }
 
 bool
@@ -2890,27 +2932,40 @@ nsDisplayBackgroundImage::Paint(nsDispla
                                 nsRenderingContext* aCtx) {
   PaintInternal(aBuilder, aCtx, mVisibleRect, &mBounds);
 }
 
 void
 nsDisplayBackgroundImage::PaintInternal(nsDisplayListBuilder* aBuilder,
                                         nsRenderingContext* aCtx, const nsRect& aBounds,
                                         nsRect* aClipRect) {
-  nsPoint offset = ToReferenceFrame();
   uint32_t flags = aBuilder->GetBackgroundPaintFlags();
   CheckForBorderItem(this, flags);
 
+  nsRect borderBox = nsRect(ToReferenceFrame(), mFrame->GetSize());
+  gfxContext* ctx = aCtx->ThebesContext();
+  uint8_t clip = mBackgroundStyle->mImage.mLayers[mLayer].mClip;
+
+  if (clip == NS_STYLE_IMAGELAYER_CLIP_TEXT) {
+    gfxRect bounds = nsLayoutUtils::RectToGfxRect(borderBox, mFrame->PresContext()->AppUnitsPerDevPixel());
+    ctx->Save();
+    ClipBackgroundByText(mFrame, aCtx, bounds);
+  }
+
   image::DrawResult result =
     nsCSSRendering::PaintBackground(mFrame->PresContext(), *aCtx, mFrame,
                                     aBounds,
-                                    nsRect(offset, mFrame->GetSize()),
+                                    borderBox,
                                     flags, aClipRect, mLayer,
                                     CompositionOp::OP_OVER);
 
+  if (clip == NS_STYLE_IMAGELAYER_CLIP_TEXT) {
+    ctx->Restore();
+  }
+
   nsDisplayBackgroundGeometry::UpdateDrawResult(this, result);
 }
 
 void nsDisplayBackgroundImage::ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
                                                          const nsDisplayItemGeometry* aGeometry,
                                                          nsRegion* aInvalidRegion)
 {
   if (!mBackgroundStyle) {
@@ -3301,29 +3356,45 @@ nsDisplayBackgroundColor::Paint(nsDispla
   nsRect borderBox = nsRect(ToReferenceFrame(), mFrame->GetSize());
 
 #if 0
   // See https://bugzilla.mozilla.org/show_bug.cgi?id=1148418#c21 for why this
   // results in a precision induced rounding issue that makes the rect one
   // pixel shorter in rare cases. Disabled in favor of the old code for now.
   // Note that the pref layout.css.devPixelsPerPx needs to be set to 1 to
   // reproduce the bug.
+  //
+  // TODO:
+  // This new path does not include support for background-clip:text; need to
+  // be fixed if/when we switch to this new code path.
+
   DrawTarget& aDrawTarget = *aCtx->GetDrawTarget();
 
   Rect rect = NSRectToSnappedRect(borderBox,
                                   mFrame->PresContext()->AppUnitsPerDevPixel(),
                                   aDrawTarget);
   ColorPattern color(ToDeviceColor(mColor));
   aDrawTarget.FillRect(rect, color);
 #else
   gfxContext* ctx = aCtx->ThebesContext();
 
   gfxRect bounds =
     nsLayoutUtils::RectToGfxRect(borderBox, mFrame->PresContext()->AppUnitsPerDevPixel());
 
+  uint8_t clip = mBackgroundStyle->mImage.mLayers[0].mClip;
+  if (clip == NS_STYLE_IMAGELAYER_CLIP_TEXT) {
+    gfxContextAutoSaveRestore save(ctx);
+
+    ClipBackgroundByText(mFrame, aCtx, bounds);
+    ctx->SetColor(mColor);
+    ctx->Fill();
+
+    return;
+  }
+
   ctx->SetColor(mColor);
   ctx->NewPath();
   ctx->Rectangle(bounds, true);
   ctx->Fill();
 #endif
 }
 
 nsRegion
--- a/layout/base/nsDisplayList.h
+++ b/layout/base/nsDisplayList.h
@@ -221,17 +221,18 @@ public:
    * @param aBuildCaret whether or not we should include the caret in any
    * display lists that we make.
    */
   enum Mode {
     PAINTING,
     EVENT_DELIVERY,
     PLUGIN_GEOMETRY,
     FRAME_VISIBILITY,
-    TRANSFORM_COMPUTATION
+    TRANSFORM_COMPUTATION,
+    GENERATE_GLYPH
   };
   nsDisplayListBuilder(nsIFrame* aReferenceFrame, Mode aMode, bool aBuildCaret);
   ~nsDisplayListBuilder();
 
   void SetWillComputePluginGeometry(bool aWillComputePluginGeometry)
   {
     mWillComputePluginGeometry = aWillComputePluginGeometry;
   }
@@ -263,16 +264,24 @@ public:
   bool IsForPainting() { return mMode == PAINTING; }
 
   /**
    * @return true if the display list is being built for determining frame
    * visibility.
    */
   bool IsForFrameVisibility() { return mMode == FRAME_VISIBILITY; }
 
+  /**
+   * @return true if the display list is being built for creating the glyph
+   * path from text items. While painting the display list, all text display
+   * items should only create glyph paths in target context, instead of
+   * drawing text into it.
+   */
+  bool IsForGenerateGlyphPath() { return mMode == GENERATE_GLYPH; }
+
   bool WillComputePluginGeometry() { return mWillComputePluginGeometry; }
   /**
    * @return true if "painting is suppressed" during page load and we
    * should paint only the background of the document.
    */
   bool IsBackgroundOnly() {
     NS_ASSERTION(mPresShellStates.Length() > 0,
                  "don't call this if we're not in a presshell");
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -1852,18 +1852,23 @@ nsFrame::DisplayBackgroundUnconditional(
 void
 nsFrame::DisplayBorderBackgroundOutline(nsDisplayListBuilder*   aBuilder,
                                         const nsDisplayListSet& aLists,
                                         bool                    aForceBackground)
 {
   // The visibility check belongs here since child elements have the
   // opportunity to override the visibility property and display even if
   // their parent is hidden.
-  if (!IsVisibleForPainting(aBuilder))
+  if (!IsVisibleForPainting(aBuilder)) {
     return;
+  }
+
+  if (aBuilder->IsForGenerateGlyphPath()) {
+    return;
+  }
 
   nsCSSShadowArray* shadows = StyleEffects()->mBoxShadow;
   if (shadows && shadows->HasShadowWithInset(false)) {
     aLists.BorderBackground()->AppendNewToTop(new (aBuilder)
       nsDisplayBoxShadowOuter(aBuilder, this));
   }
 
   bool bgIsThemed = DisplayBackgroundUnconditional(aBuilder, aLists,
@@ -2549,20 +2554,26 @@ nsIFrame::BuildDisplayListForChild(nsDis
                                    const nsRect&           aDirtyRect,
                                    const nsDisplayListSet& aLists,
                                    uint32_t                aFlags) {
   // If painting is restricted to just the background of the top level frame,
   // then we have nothing to do here.
   if (aBuilder->IsBackgroundOnly())
     return;
 
+  if (aBuilder->IsForGenerateGlyphPath()) {
+    if (nsGkAtoms::textFrame != aChild->GetType() && aChild->IsLeaf()) {
+      return;
+    }
+  }
+
   nsIFrame* child = aChild;
   if (child->GetStateBits() & NS_FRAME_TOO_DEEP_IN_FRAME_TREE)
     return;
-  
+
   bool isSVG = (child->GetStateBits() & NS_FRAME_SVG_LAYOUT);
 
   // true if this is a real or pseudo stacking context
   bool pseudoStackingContext =
     (aFlags & DISPLAY_CHILD_FORCE_PSEUDO_STACKING_CONTEXT) != 0;
   if (!isSVG &&
       (aFlags & DISPLAY_CHILD_INLINE) &&
       !child->IsFrameOfType(eLineParticipant)) {
--- a/layout/generic/nsTextFrame.cpp
+++ b/layout/generic/nsTextFrame.cpp
@@ -276,18 +276,16 @@ struct TextRunUserData {
  * underline information. The data is computed lazily and cached as necessary.
  * These live for just the duration of one paint operation.
  */
 class nsTextPaintStyle {
 public:
   explicit nsTextPaintStyle(nsTextFrame* aFrame);
 
   void SetResolveColors(bool aResolveColors) {
-    NS_ASSERTION(mFrame->IsSVGText() || aResolveColors,
-                 "must resolve colors is frame is not for SVG text");
     mResolveColors = aResolveColors;
   }
 
   nscolor GetTextColor();
   /**
    * Compute the colors for normally-selected text. Returns false if
    * the normal selection is not being displayed.
    */
@@ -4794,27 +4792,35 @@ nsDisplayText::Paint(nsDisplayListBuilde
   gfxContext* ctx = aCtx->ThebesContext();
   gfxContextAutoSaveRestore save(ctx);
 
   gfxRect pixelVisible(extraVisible.x, extraVisible.y,
                        extraVisible.width, extraVisible.height);
   pixelVisible.Inflate(2);
   pixelVisible.RoundOut();
 
-  ctx->NewPath();
-  ctx->Rectangle(pixelVisible);
-  ctx->Clip();
+  if (!aBuilder->IsForGenerateGlyphPath()) {
+    ctx->NewPath();
+    ctx->Rectangle(pixelVisible);
+    ctx->Clip();
+  }
 
   NS_ASSERTION(mVisIStartEdge >= 0, "illegal start edge");
   NS_ASSERTION(mVisIEndEdge >= 0, "illegal end edge");
 
   nsPoint framePt = ToReferenceFrame();
   nsTextFrame::PaintTextParams params(aCtx->ThebesContext());
   params.framePt = gfxPoint(framePt.x, framePt.y);
+
   params.dirtyRect = extraVisible;
+  nsTextFrame::DrawPathCallbacks callbacks;
+  if (aBuilder->IsForGenerateGlyphPath()) {
+    params.callbacks = &callbacks;
+  }
+
   f->PaintText(params, *this, mOpacity);
 }
 
 void
 nsTextFrame::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
                               const nsRect&           aDirtyRect,
                               const nsDisplayListSet& aLists)
 {
--- a/layout/generic/nsTextFrame.h
+++ b/layout/generic/nsTextFrame.h
@@ -382,16 +382,18 @@ public:
      */
     virtual void NotifyBeforeSelectionDecorationLine(nscolor aColor) { }
 
     /**
      * Called just after a path corresponding to a selection decoration line
      * has been emitted to the gfxContext.
      */
     virtual void NotifySelectionDecorationLinePathEmitted() { }
+
+    virtual void NotifyGlyphPathEmitted() override {}
   };
 
   struct PaintTextParams
   {
     gfxContext* context;
     gfxPoint framePt;
     LayoutDeviceRect dirtyRect;
     gfxTextContextPaint* contextPaint = nullptr;