Bug 686281 - Mask CSS rendering; r=mstange draft
authorCJKu <cku@mozilla.com>
Thu, 10 Dec 2015 09:43:10 -0500
changeset 314886 fd7cd1d9e2b17341cce0778c7073a0f88bc7bc20
parent 314885 5b5965fd78b0c8d2da986884876ac4fd066e77c3
child 314887 ecd23623143ed4d8c99271e4fe83a32454b7a0a6
push id8289
push usercku@mozilla.com
push dateThu, 10 Dec 2015 14:43:35 +0000
reviewersmstange
bugs686281
milestone45.0a1
Bug 686281 - Mask CSS rendering; r=mstange
layout/base/nsCSSRendering.cpp
layout/base/nsCSSRendering.h
layout/base/nsDisplayList.cpp
layout/svg/nsSVGIntegrationUtils.cpp
layout/svg/nsSVGIntegrationUtils.h
--- a/layout/base/nsCSSRendering.cpp
+++ b/layout/base/nsCSSRendering.cpp
@@ -2897,20 +2897,23 @@ nsCSSRendering::PaintBackgroundWithSC(ns
   bool drawBackgroundColor;
 
   nscolor bgColor = DetermineBackgroundColor(aPresContext,
                                              aBackgroundSC,
                                              aForFrame,
                                              drawBackgroundImage,
                                              drawBackgroundColor);
 
-  const nsStyleImageLayers& layers = aBackgroundSC->StyleBackground()->mLayers;
+  bool paintMask = (aFlags & PAINTBG_MASK_IMAGE);
+  const nsStyleImageLayers& layers = paintMask ?
+    aBackgroundSC->StyleSVGReset()->mLayers :
+    aBackgroundSC->StyleBackground()->mLayers;
   // If we're drawing a specific layer, we don't want to draw the
   // background color.
-  if (drawBackgroundColor && aLayer >= 0) {
+  if ((drawBackgroundColor && aLayer >= 0) || paintMask) {
     drawBackgroundColor = false;
   }
 
   // At this point, drawBackgroundImage and drawBackgroundColor are
   // true if and only if we are actually supposed to paint an image or
   // color into aDirtyRect, respectively.
   if (!drawBackgroundImage && !drawBackgroundColor)
     return DrawResult::SUCCESS;
@@ -3023,21 +3026,26 @@ nsCSSRendering::PaintBackgroundWithSC(ns
             ctx->NewPath();
             ctx->SnappedRectangle(clip);
             ctx->Clip();
           }
         }
       }
       if ((aLayer < 0 || i == (uint32_t)startLayer) &&
           !clipState.mDirtyRectGfx.IsEmpty()) {
-        nsBackgroundLayerState state = PrepareImageLayer(aPresContext, aForFrame,
-            aFlags, paintBorderArea, clipState.mBGClipArea, layer);
+        nsBackgroundLayerState state =
+          PrepareImageLayer(aPresContext, aForFrame,
+                            aFlags, paintBorderArea, clipState.mBGClipArea,
+                            layer, paintMask);
         result &= state.mImageRenderer.PrepareResult();
         if (!state.mFillArea.IsEmpty()) {
-          if (state.mCompositionOp != CompositionOp::OP_OVER) {
+          // Always using OP_OVER mode while drawing the bottom mask layer.
+          bool isFirstLayer = (i == (layers.mImageCount - 1));
+          if (state.mCompositionOp != CompositionOp::OP_OVER &&
+              (!paintMask || !isFirstLayer)) {
             NS_ASSERTION(ctx->CurrentOp() == CompositionOp::OP_OVER,
                          "It is assumed the initial op is OP_OVER, when it is restored later");
             ctx->SetOp(state.mCompositionOp);
           }
 
           result &=
             state.mImageRenderer.DrawBackground(aPresContext, aRenderingContext,
                                                 state.mDestArea, state.mFillArea,
@@ -3206,17 +3214,18 @@ ComputeDrawnSizeForBackground(const CSSS
 }
 
 nsBackgroundLayerState
 nsCSSRendering::PrepareImageLayer(nsPresContext* aPresContext,
                                   nsIFrame* aForFrame,
                                   uint32_t aFlags,
                                   const nsRect& aBorderArea,
                                   const nsRect& aBGClipRect,
-                                  const nsStyleImageLayers::Layer& aLayer)
+                                  const nsStyleImageLayers::Layer& aLayer,
+                                  bool aMask)
 {
   /*
    * The properties we need to keep in mind when drawing style
    * layers are:
    *
    *   background-image/ mask-image
    *   background-repeat/ mask-repeat
    *   background-attachment
@@ -3368,17 +3377,18 @@ nsCSSRendering::PrepareImageLayer(nsPres
     } else {
       repeatMode = ExtendMode::REPEAT_Y;
     }
   }
   state.mImageRenderer.SetExtendMode(repeatMode);
 
   state.mFillArea.IntersectRect(state.mFillArea, bgClipRect);
 
-  state.mCompositionOp = GetGFXBlendMode(aLayer.mBlendMode);
+  state.mCompositionOp = aMask ? GetGFXCompositeMode(aLayer.mComposite) :
+                                 GetGFXBlendMode(aLayer.mBlendMode);
 
   return state;
 }
 
 nsRect
 nsCSSRendering::GetBackgroundLayerRect(nsPresContext* aPresContext,
                                        nsIFrame* aForFrame,
                                        const nsRect& aBorderArea,
--- a/layout/base/nsCSSRendering.h
+++ b/layout/base/nsCSSRendering.h
@@ -540,17 +540,18 @@ struct nsCSSRendering {
                                    nsIFrame** aAttachedToFrame);
 
   static nsBackgroundLayerState
   PrepareImageLayer(nsPresContext* aPresContext,
                     nsIFrame* aForFrame,
                     uint32_t aFlags,
                     const nsRect& aBorderArea,
                     const nsRect& aBGClipRect,
-                    const nsStyleImageLayers::Layer& aLayer);
+                    const nsStyleImageLayers::Layer& aLayer,
+                    bool aMask = false);
 
   struct ImageLayerClipState {
     nsRect mBGClipArea;  // Affected by mClippedRadii
     nsRect mAdditionalBGClipArea;  // Not affected by mClippedRadii
     nsRect mDirtyRect;
     gfxRect mDirtyRectGfx;
 
     nscoord mRadii[8];
@@ -567,43 +568,49 @@ struct nsCSSRendering {
   GetImageLayerClip(const nsStyleImageLayers::Layer& aLayer,
                     nsIFrame* aForFrame, const nsStyleBorder& aBorder,
                     const nsRect& aBorderArea, const nsRect& aCallerDirtyRect,
                     bool aWillPaintBorder, nscoord aAppUnitsPerPixel,
                     /* out */ ImageLayerClipState* aClipState);
 
   /**
    * Render the background for an element using css rendering rules
-   * for backgrounds.
+   * for backgrounds or mask.
    */
   enum {
     /**
      * When this flag is passed, the element's nsDisplayBorder will be
      * painted immediately on top of this background.
      */
     PAINTBG_WILL_PAINT_BORDER = 0x01,
     /**
      * When this flag is passed, images are synchronously decoded.
      */
     PAINTBG_SYNC_DECODE_IMAGES = 0x02,
     /**
      * When this flag is passed, painting will go to the screen so we can
      * take advantage of the fact that it will be clipped to the viewport.
      */
-    PAINTBG_TO_WINDOW = 0x04
+    PAINTBG_TO_WINDOW = 0x04,
+    /**
+     * When this flag is passed, painting will read properties of mask-image
+     * style, instead of background-image.
+     */
+    PAINTBG_MASK_IMAGE = 0x08
   };
   static DrawResult PaintBackground(nsPresContext* aPresContext,
                                     nsRenderingContext& aRenderingContext,
                                     nsIFrame* aForFrame,
                                     const nsRect& aDirtyRect,
                                     const nsRect& aBorderArea,
                                     uint32_t aFlags,
                                     nsRect* aBGClipRect = nullptr,
                                     int32_t aLayer = -1);
 
+
   /**
    * Same as |PaintBackground|, except using the provided style structs.
    * This short-circuits the code that ensures that the root element's
    * background is drawn on the canvas.
    * The aLayer parameter allows you to paint a single layer of the background.
    * The default value for aLayer, -1, means that all layers will be painted.
    * The background color will only be painted if the back-most layer is also
    * being painted.
@@ -791,16 +798,26 @@ struct nsCSSRendering {
       case NS_STYLE_BLEND_HUE:         return CompositionOp::OP_HUE;
       case NS_STYLE_BLEND_SATURATION:  return CompositionOp::OP_SATURATION;
       case NS_STYLE_BLEND_COLOR:       return CompositionOp::OP_COLOR;
       case NS_STYLE_BLEND_LUMINOSITY:  return CompositionOp::OP_LUMINOSITY;
       default:      MOZ_ASSERT(false); return CompositionOp::OP_OVER;
     }
   }
 
+  static CompositionOp GetGFXCompositeMode(uint8_t aCompositeMode) {
+    switch (aCompositeMode) {
+      case NS_STYLE_COMPOSITE_MODE_ADD:        return CompositionOp::OP_OVER;
+      case NS_STYLE_COMPOSITE_MODE_SUBSTRACT:  return CompositionOp::OP_OUT;
+      case NS_STYLE_COMPOSITE_MODE_INTERSECT:  return CompositionOp::OP_IN;
+      case NS_STYLE_COMPOSITE_MODE_EXCLUDE:    return CompositionOp::OP_XOR;
+      default:              MOZ_ASSERT(false); return CompositionOp::OP_OVER;
+    }
+
+  }
 protected:
   static gfxRect GetTextDecorationRectInternal(const Point& aPt,
                                                const Size& aLineSize,
                                                const gfxFloat aAscent,
                                                const gfxFloat aOffset,
                                                const uint8_t aDecoration,
                                                const uint8_t aStyle,
                                                bool aVertical,
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -6218,18 +6218,20 @@ nsDisplaySVGEffects::HitTest(nsDisplayLi
   }
 }
 
 void
 nsDisplaySVGEffects::PaintAsLayer(nsDisplayListBuilder* aBuilder,
                                   nsRenderingContext* aCtx,
                                   LayerManager* aManager)
 {
+  nsRect borderArea = nsRect(ToReferenceFrame(), mFrame->GetSize());
   nsSVGIntegrationUtils::PaintFramesWithEffects(*aCtx->ThebesContext(), mFrame,
                                                 mVisibleRect,
+                                                borderArea,
                                                 aBuilder, aManager);
 }
 
 LayerState
 nsDisplaySVGEffects::GetLayerState(nsDisplayListBuilder* aBuilder,
                                    LayerManager* aManager,
                                    const ContainerLayerParameters& aParameters)
 {
@@ -6260,17 +6262,16 @@ nsDisplaySVGEffects::BuildLayer(nsDispla
 
   nsIFrame* firstFrame =
     nsLayoutUtils::FirstContinuationOrIBSplitSibling(mFrame);
   nsSVGEffects::EffectProperties effectProperties =
     nsSVGEffects::GetEffectProperties(firstFrame);
 
   bool isOK = effectProperties.HasNoFilterOrHasValidFilter();
   effectProperties.GetClipPathFrame(&isOK);
-  effectProperties.GetMaskFrame(&isOK);
 
   if (!isOK) {
     return nullptr;
   }
 
   ContainerLayerParameters newContainerParameters = aContainerParameters;
   if (effectProperties.HasValidFilter()) {
     newContainerParameters.mDisableSubpixelAntialiasingInDescendants = true;
--- a/layout/svg/nsSVGIntegrationUtils.cpp
+++ b/layout/svg/nsSVGIntegrationUtils.cpp
@@ -18,16 +18,17 @@
 #include "nsSVGElement.h"
 #include "nsSVGFilterPaintCallback.h"
 #include "nsSVGMaskFrame.h"
 #include "nsSVGPaintServerFrame.h"
 #include "nsSVGUtils.h"
 #include "FrameLayerBuilder.h"
 #include "BasicLayers.h"
 #include "mozilla/gfx/Point.h"
+#include "nsCSSRendering.h"
 
 using namespace mozilla;
 using namespace mozilla::layers;
 using namespace mozilla::gfx;
 
 // ----------------------------------------------------------------------
 
 /**
@@ -147,18 +148,21 @@ GetPreEffectsVisualOverflowUnion(nsIFram
 bool
 nsSVGIntegrationUtils::UsingEffectsForFrame(const nsIFrame* aFrame)
 {
   // Even when SVG display lists are disabled, returning true for SVG frames
   // does not adversely affect any of our callers. Therefore we don't bother
   // checking the SDL prefs here, since we don't know if we're being called for
   // painting or hit-testing anyway.
   const nsStyleSVGReset *style = aFrame->StyleSVGReset();
-  return (style->HasFilters() ||
-          style->mClipPath.GetType() != NS_STYLE_CLIP_PATH_NONE || style->mMask);
+  bool hasValidLayers = style->mLayers.HasValidLayers();
+
+  return (style->HasFilters() || style->mMask ||
+          (style->mClipPath.GetType() != NS_STYLE_CLIP_PATH_NONE) ||
+          hasValidLayers);
 }
 
 // For non-SVG frames, this gives the offset to the frame's "user space".
 // For SVG frames, this returns a zero offset.
 static nsPoint
 GetOffsetToBoundingBox(nsIFrame* aFrame)
 {
   if ((aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT)) {
@@ -404,16 +408,17 @@ private:
   LayerManager* mLayerManager;
   nsPoint mOffset;
 };
 
 void
 nsSVGIntegrationUtils::PaintFramesWithEffects(gfxContext& aContext,
                                               nsIFrame* aFrame,
                                               const nsRect& aDirtyRect,
+                                              const nsRect& aBorderArea,
                                               nsDisplayListBuilder* aBuilder,
                                               LayerManager *aLayerManager)
 {
 #ifdef DEBUG
   NS_ASSERTION(!(aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT) ||
                (NS_SVGDisplayListPaintingEnabled() &&
                 !(aFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY)),
                "Should not use nsSVGIntegrationUtils on this SVG frame");
@@ -459,20 +464,16 @@ nsSVGIntegrationUtils::PaintFramesWithEf
      so make sure all applicable ones are set again. */
   nsIFrame* firstFrame =
     nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
   nsSVGEffects::EffectProperties effectProperties =
     nsSVGEffects::GetEffectProperties(firstFrame);
 
   bool isOK = effectProperties.HasNoFilterOrHasValidFilter();
   nsSVGClipPathFrame *clipPathFrame = effectProperties.GetClipPathFrame(&isOK);
-  nsSVGMaskFrame *maskFrame = effectProperties.GetMaskFrame(&isOK);
-  if (!isOK) {
-    return; // Some resource is missing. We shouldn't paint anything.
-  }
 
   bool isTrivialClip = clipPathFrame ? clipPathFrame->IsTrivial() : true;
 
   DrawTarget* drawTarget = aContext.GetDrawTarget();
   gfxContextMatrixAutoSaveRestore matrixAutoSaveRestore(&aContext);
 
   nsPoint firstFrameOffset = GetOffsetToBoundingBox(firstFrame);
   nsPoint offsetToBoundingBox = aBuilder->ToReferenceFrame(firstFrame) - firstFrameOffset;
@@ -505,77 +506,132 @@ nsSVGIntegrationUtils::PaintFramesWithEf
 
   gfxPoint devPixelOffsetToUserSpace =
     nsLayoutUtils::PointToGfxPoint(offsetToUserSpace,
                                    aFrame->PresContext()->AppUnitsPerDevPixel());
   aContext.SetMatrix(aContext.CurrentMatrix().Translate(devPixelOffsetToUserSpace));
 
   gfxMatrix cssPxToDevPxMatrix = GetCSSPxToDevPxMatrix(aFrame);
 
+  const nsStyleSVGReset *svgReset = firstFrame->StyleSVGReset();
+  // Keep moving forward even if svgMaskFrame is nullptr or isOK is false.
+  // This source is not a svg mask, but it still can be a correct mask image.
+  nsSVGMaskFrame *svgMaskFrame = effectProperties.GetMaskFrame(&isOK);
+
   bool complexEffects = false;
+  bool hasValidLayers = svgReset->mLayers.HasValidLayers();
 
   // These are used if we require a temporary surface for a custom blend mode.
   RefPtr<gfxContext> target = &aContext;
   IntPoint targetOffset;
 
   /* Check if we need to do additional operations on this child's
    * rendering, which necessitates rendering into another surface. */
-  if (opacity != 1.0f || maskFrame || (clipPathFrame && !isTrivialClip)
-      || aFrame->StyleDisplay()->mMixBlendMode != NS_STYLE_BLEND_NORMAL) {
+  if (opacity != 1.0f ||  (clipPathFrame && !isTrivialClip)
+      || aFrame->StyleDisplay()->mMixBlendMode != NS_STYLE_BLEND_NORMAL
+      || svgMaskFrame || hasValidLayers) {
     complexEffects = true;
 
-    Matrix maskTransform;
-    RefPtr<SourceSurface> maskSurface =
-      maskFrame ? maskFrame->GetMaskForMaskedFrame(&aContext,
-                                                    aFrame, cssPxToDevPxMatrix, opacity, &maskTransform)
-                : nullptr;
-
-    if (maskFrame && !maskSurface) {
-      // Entire surface is clipped out.
-      return;
-    }
-
     aContext.Save();
     nsRect clipRect =
       aFrame->GetVisualOverflowRectRelativeToSelf() + toUserSpace;
     aContext.Clip(NSRectToSnappedRect(clipRect,
                                   aFrame->PresContext()->AppUnitsPerDevPixel(),
                                   *drawTarget));
 
+    Matrix maskTransform;
+    RefPtr<SourceSurface> maskSurface;
+    if (svgMaskFrame) {
+      maskSurface = svgMaskFrame->GetMaskForMaskedFrame(&aContext,
+                                                     aFrame,
+                                                     cssPxToDevPxMatrix,
+                                                     opacity,
+                                                     &maskTransform);
+    } else if (hasValidLayers) {
+      gfxRect clipRect = aContext.GetClipExtents();
+      {
+        gfxContextMatrixAutoSaveRestore matRestore(&aContext);
+
+        aContext.SetMatrix(gfxMatrix());
+        clipRect = aContext.GetClipExtents();
+      }
+      IntRect drawRect = RoundedOut(ToRect(clipRect));
+      RefPtr<DrawTarget> targetDT = aContext.GetDrawTarget()->CreateSimilarDrawTarget(drawRect.Size(), SurfaceFormat::A8);
+      if (!targetDT) {
+        aContext.Restore();
+        return;
+      }
+
+      RefPtr<gfxContext> target = new gfxContext(targetDT);
+      target->SetMatrix(matrixAutoSaveRestore.Matrix() * gfxMatrix::Translation(-drawRect.TopLeft()));
+
+      // Generate mask surface.
+      uint32_t flags = aBuilder->GetBackgroundPaintFlags() |
+                       nsCSSRendering::PAINTBG_MASK_IMAGE;
+      nsRenderingContext rc(target);
+      nsCSSRendering::PaintBackgroundWithSC(aFrame->PresContext(),
+                                            rc,
+                                            aFrame,
+                                            aDirtyRect,
+                                            aBorderArea,
+                                            firstFrame->StyleContext(),
+                                            *aFrame->StyleBorder(),
+                                            flags,
+                                            nullptr,
+                                            -1);
+      maskSurface = targetDT->Snapshot();
+
+      // Compute mask transform.
+      Matrix mat = ToMatrix(aContext.CurrentMatrix());
+      mat.Invert();
+      maskTransform = Matrix::Translation(drawRect.x, drawRect.y) * mat;
+    }
+
+    if ((svgMaskFrame || hasValidLayers) && !maskSurface) {
+      // Entire surface is clipped out.
+      aContext.Restore();
+      return;
+    }
+
     if (aFrame->StyleDisplay()->mMixBlendMode != NS_STYLE_BLEND_NORMAL) {
       // Create a temporary context to draw to so we can blend it back with
       // another operator.
       gfxRect clipRect;
       {
         gfxContextMatrixAutoSaveRestore matRestore(&aContext);
 
         aContext.SetMatrix(gfxMatrix());
         clipRect = aContext.GetClipExtents();
       }
 
       IntRect drawRect = RoundedOut(ToRect(clipRect));
 
       RefPtr<DrawTarget> targetDT = aContext.GetDrawTarget()->CreateSimilarDrawTarget(drawRect.Size(), SurfaceFormat::B8G8R8A8);
+      if (!targetDT) {
+        aContext.Restore();
+        return;
+      }
       target = new gfxContext(targetDT);
       target->SetMatrix(aContext.CurrentMatrix() * gfxMatrix::Translation(-drawRect.TopLeft()));
       targetOffset = drawRect.TopLeft();
     }
 
     if (clipPathFrame && !isTrivialClip) {
       Matrix clippedMaskTransform;
       RefPtr<SourceSurface> clipMaskSurface = clipPathFrame->GetClipMask(aContext, aFrame, cssPxToDevPxMatrix,
                                                                          &clippedMaskTransform, maskSurface, maskTransform);
 
       if (clipMaskSurface) {
         maskSurface = clipMaskSurface;
         maskTransform = clippedMaskTransform;
       }
     }
 
-    if (opacity != 1.0f || maskFrame || (clipPathFrame && !isTrivialClip)) {
+    if (opacity != 1.0f || svgMaskFrame  || hasValidLayers ||
+        (clipPathFrame && !isTrivialClip)) {
       target->PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, opacity, maskSurface, maskTransform);
     }
   }
 
   /* If this frame has only a trivial clipPath, set up cairo's clipping now so
    * we can just do normal painting and get it clipped appropriately.
    */
   if (clipPathFrame && isTrivialClip) {
@@ -604,17 +660,18 @@ nsSVGIntegrationUtils::PaintFramesWithEf
     aContext.Restore();
   }
 
   /* No more effects, we're done. */
   if (!complexEffects) {
     return;
   }
 
-  if (opacity != 1.0f || maskFrame || (clipPathFrame && !isTrivialClip)) {
+  if (opacity != 1.0f || svgMaskFrame || hasValidLayers ||
+      (clipPathFrame && !isTrivialClip)) {
     target->PopGroupAndBlend();
   }
 
   if (aFrame->StyleDisplay()->mMixBlendMode != NS_STYLE_BLEND_NORMAL) {
     RefPtr<DrawTarget> targetDT = target->GetDrawTarget();
     target = nullptr;
     RefPtr<SourceSurface> targetSurf = targetDT->Snapshot();
 
--- a/layout/svg/nsSVGIntegrationUtils.h
+++ b/layout/svg/nsSVGIntegrationUtils.h
@@ -124,16 +124,17 @@ public:
   HitTestFrameForEffects(nsIFrame* aFrame, const nsPoint& aPt);
 
   /**
    * Paint non-SVG frame with SVG effects.
    */
   static void
   PaintFramesWithEffects(gfxContext& aCtx,
                          nsIFrame* aFrame, const nsRect& aDirtyRect,
+                         const nsRect& aBorderArea,
                          nsDisplayListBuilder* aBuilder,
                          mozilla::layers::LayerManager* aManager);
 
   /**
    * SVG frames expect to paint in SVG user units, which are equal to CSS px
    * units. This method provides a transform matrix to multiply onto a
    * gfxContext's current transform to convert the context's current units from
    * its usual dev pixels to SVG user units/CSS px to keep the SVG code happy.