Bug 1319667 - Part 1. Allow mask longhands apply to SVG elements. draft
authorcku <cku@mozilla.com>
Wed, 23 Nov 2016 13:51:36 +0800
changeset 443251 ffb1272b5f3c3408ca950633b62f7903691d5924
parent 443004 47f42f21541b9b98ad7db82edb996b29065debd0
child 443252 09e0ae89fd388cca36a0a3d67cb26308101d4e76
push id36940
push userbmo:cku@mozilla.com
push dateThu, 24 Nov 2016 04:14:24 +0000
bugs1319667
milestone53.0a1
Bug 1319667 - Part 1. Allow mask longhands apply to SVG elements. Originally, we use the follwoing statement to determine whether generate mask for an SVG element: aUsage.shouldGenerateMaskLayer = maskFrames.Length() == 1 && maskFrames[0]; maskFrames[0] is not null only if that mask resource is an SVG-mask. That means we will not generate mask for image mask to any SVG one. MozReview-Commit-ID: 4QiifC6J0UR
layout/svg/nsSVGIntegrationUtils.cpp
layout/svg/nsSVGUtils.cpp
--- a/layout/svg/nsSVGIntegrationUtils.cpp
+++ b/layout/svg/nsSVGIntegrationUtils.cpp
@@ -509,80 +509,118 @@ PaintMaskSurface(const PaintFramesParams
         return result;
       }
     }
   }
 
   return DrawResult::SUCCESS;
 }
 
-static DrawResult
+struct MaskPaintResult {
+  RefPtr<SourceSurface> maskSurface;
+  Matrix maskTransform;
+  DrawResult result;
+  bool transparentBlackMask;
+  bool opacityApplied;
+
+  MaskPaintResult()
+    : result(DrawResult::SUCCESS), transparentBlackMask(false),
+      opacityApplied(false)
+  {}
+};
+
+static MaskPaintResult
 CreateAndPaintMaskSurface(const PaintFramesParams& aParams,
                           float aOpacity, nsStyleContext* aSC,
                           const nsTArray<nsSVGMaskFrame*>& aMaskFrames,
-                          const nsPoint& aOffsetToUserSpace,
-                          Matrix& aOutMaskTransform,
-                          RefPtr<SourceSurface>& aOutMaskSurface,
-                          bool& aOpacityApplied)
+                          const nsPoint& aOffsetToUserSpace)
 {
   const nsStyleSVGReset *svgReset = aSC->StyleSVGReset();
   MOZ_ASSERT(aMaskFrames.Length() > 0);
+  MaskPaintResult paintResult;
 
   gfxContext& ctx = aParams.ctx;
-  DrawResult result = DrawResult::SUCCESS;
 
-  // There is only one SVG mask.
+  // Optimization for single SVG mask.
   if (((aMaskFrames.Length() == 1) && aMaskFrames[0])) {
     gfxMatrix cssPxToDevPxMatrix =
     nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(aParams.frame);
+    paintResult.opacityApplied = true;
     nsSVGMaskFrame::MaskParams params(&ctx, aParams.frame, cssPxToDevPxMatrix,
-                                      aOpacity, &aOutMaskTransform,
+                                      aOpacity, &paintResult.maskTransform,
                                       svgReset->mMask.mLayers[0].mMaskMode);
-    aOpacityApplied = true;
-    Tie(result, aOutMaskSurface) =
+    Tie(paintResult.result, paintResult.maskSurface) =
       aMaskFrames[0]->GetMaskForMaskedFrame(params);
-    return result;
+
+    if (!paintResult.maskSurface) {
+      paintResult.transparentBlackMask = true;
+    }
+
+    return paintResult;
   }
 
   const IntRect& maskSurfaceRect = aParams.maskRect;
   if (maskSurfaceRect.IsEmpty()) {
-    return DrawResult::SUCCESS;
+    paintResult.transparentBlackMask = true;
+    return paintResult;
   }
 
   RefPtr<DrawTarget> maskDT =
       ctx.GetDrawTarget()->CreateSimilarDrawTarget(maskSurfaceRect.Size(),
                                                    SurfaceFormat::A8);
   if (!maskDT || !maskDT->IsValid()) {
-    return DrawResult::TEMPORARY_ERROR;
+    paintResult.result = DrawResult::TEMPORARY_ERROR;
+    return paintResult;
   }
 
   // Set aAppliedOpacity as true only if all mask layers are svg mask.
   // In this case, we will apply opacity into the final mask surface, so the
   // caller does not need to apply it again.
-  aOpacityApplied = !HasNonSVGMask(aMaskFrames);
+  paintResult.opacityApplied = !HasNonSVGMask(aMaskFrames);
 
   // Set context's matrix on maskContext, offset by the maskSurfaceRect's
   // position. This makes sure that we combine the masks in device space.
   gfxMatrix maskSurfaceMatrix =
     ctx.CurrentMatrix() * gfxMatrix::Translation(-aParams.maskRect.TopLeft());
 
-  result = PaintMaskSurface(aParams, maskDT, aOpacityApplied ? aOpacity : 1.0,
-                            aSC, aMaskFrames, maskSurfaceMatrix,
-                            aOffsetToUserSpace);
-  if (result != DrawResult::SUCCESS) {
-    return result;
+  paintResult.result = PaintMaskSurface(aParams, maskDT,
+                                        paintResult.opacityApplied
+                                          ? aOpacity : 1.0,
+                                        aSC, aMaskFrames, maskSurfaceMatrix,
+                                        aOffsetToUserSpace);
+  if (paintResult.result != DrawResult::SUCCESS) {
+    // Now we know the status of mask resource since we used it while painting.
+    // According to the return value of PaintMaskSurface, we know whether mask
+    // resource is resolvable or not.
+    //
+    // For a HTML doc:
+    //   According to css-masking spec, always create a mask surface when
+    //   we have any item in maskFrame even if all of those items are
+    //   non-resolvable <mask-sources> or <images>.
+    //   Set paintResult.transparentBlackMask as true,  the caller should stop
+    //   painting masked content as if this mask is a transparent black one.
+    // For a SVG doc:
+    //   SVG 1.1 say that if we fail to resolve a mask, we should draw the
+    //   object unmasked.
+    //   Left patinResult.maskSurface empty, the caller should paint all
+    //   masked content as if this mask is an opaque white one(no mask).
+    paintResult.transparentBlackMask =
+      !(aParams.frame->GetStateBits() & NS_FRAME_SVG_LAYOUT);
+
+    MOZ_ASSERT(!paintResult.maskSurface);
+    return paintResult;
   }
 
-  aOutMaskTransform = ToMatrix(maskSurfaceMatrix);
-  if (!aOutMaskTransform.Invert()) {
-    return DrawResult::SUCCESS;
+  paintResult.maskTransform = ToMatrix(maskSurfaceMatrix);
+  if (!paintResult.maskTransform.Invert()) {
+    return paintResult;
   }
 
-  aOutMaskSurface = maskDT->Snapshot();
-  return DrawResult::SUCCESS;
+  paintResult.maskSurface = maskDT->Snapshot();
+  return paintResult;
 }
 
 static bool
 ValidateSVGFrame(nsIFrame* aFrame)
 {
 #ifdef DEBUG
   NS_ASSERTION(!(aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT) ||
                (NS_SVGDisplayListPaintingEnabled() &&
@@ -824,16 +862,17 @@ nsSVGIntegrationUtils::PaintMaskAndClipP
   nsTArray<nsSVGMaskFrame*> maskFrames = effectProperties.GetMaskFrames();
 
   nsPoint offsetToBoundingBox;
   nsPoint offsetToUserSpace;
 
   bool shouldGenerateMask = (maskUsage.opacity != 1.0f ||
                              maskUsage.shouldGenerateClipMaskLayer ||
                              maskUsage.shouldGenerateMaskLayer);
+  bool shouldPushMask = false;
 
   /* Check if we need to do additional operations on this child's
    * rendering, which necessitates rendering into another surface. */
   if (shouldGenerateMask) {
     gfxContextMatrixAutoSaveRestore matSR;
 
     Matrix maskTransform;
     RefPtr<SourceSurface> maskSurface;
@@ -842,24 +881,33 @@ nsSVGIntegrationUtils::PaintMaskAndClipP
     if (maskUsage.shouldGenerateMaskLayer) {
       matSR.SetContext(&context);
 
       // For css-mask, we want to generate a mask for each continuation frame,
       // so we setup context matrix by the position of the current frame,
       // instead of the first continuation frame.
       SetupContextMatrix(frame, aParams, offsetToBoundingBox,
                          offsetToUserSpace, false);
-      result = CreateAndPaintMaskSurface(aParams, maskUsage.opacity,
-                                         firstFrame->StyleContext(),
-                                         maskFrames, offsetToUserSpace,
-                                         maskTransform, maskSurface,
-                                         opacityApplied);
-      if (!maskSurface) {
-        // Entire surface is clipped out.
-        return result;
+      MaskPaintResult paintResult =
+        CreateAndPaintMaskSurface(aParams, maskUsage.opacity,
+                                  firstFrame->StyleContext(),
+                                  maskFrames, offsetToUserSpace);
+
+      if (paintResult.transparentBlackMask) {
+        MOZ_ASSERT(paintResult.result != DrawResult::SUCCESS);
+        return paintResult.result;
+      }
+
+      result &= paintResult.result;
+      maskSurface = paintResult.maskSurface;
+      if (maskSurface) {
+        MOZ_ASSERT(paintResult.result == DrawResult::SUCCESS);
+        shouldPushMask = true;
+        maskTransform = paintResult.maskTransform;
+        opacityApplied = paintResult.opacityApplied;
       }
     }
 
     if (maskUsage.shouldGenerateClipMaskLayer) {
       matSR.Restore();
       matSR.SetContext(&context);
 
       SetupContextMatrix(firstFrame, aParams, offsetToBoundingBox,
@@ -873,38 +921,44 @@ nsSVGIntegrationUtils::PaintMaskAndClipP
       if (clipMaskSurface) {
         maskSurface = clipMaskSurface;
         maskTransform = clipMaskTransform;
       } else {
         // Either entire surface is clipped out, or gfx buffer allocation
         // failure in nsSVGClipPathFrame::GetClipMask.
         return result;
       }
+
+      shouldPushMask = true;
     }
 
     // opacity != 1.0f.
     if (!maskUsage.shouldGenerateClipMaskLayer &&
         !maskUsage.shouldGenerateMaskLayer) {
       MOZ_ASSERT(maskUsage.opacity != 1.0f);
 
       matSR.SetContext(&context);
       SetupContextMatrix(firstFrame, aParams, offsetToBoundingBox,
                          offsetToUserSpace, false);
+      shouldPushMask = true;
     }
 
-    if (aParams.layerManager->GetRoot()->GetContentFlags() & Layer::CONTENT_COMPONENT_ALPHA) {
-      context.PushGroupAndCopyBackground(gfxContentType::COLOR_ALPHA,
-                                         opacityApplied
-                                           ? 1.0
-                                           : maskUsage.opacity,
-                                         maskSurface, maskTransform);
-    } else {
-      context.PushGroupForBlendBack(gfxContentType::COLOR_ALPHA,
-                                    opacityApplied ? 1.0 : maskUsage.opacity,
-                                    maskSurface, maskTransform);
+    if (shouldPushMask) {
+      if (aParams.layerManager->GetRoot()->GetContentFlags() &
+          Layer::CONTENT_COMPONENT_ALPHA) {
+        context.PushGroupAndCopyBackground(gfxContentType::COLOR_ALPHA,
+                                           opacityApplied
+                                             ? 1.0
+                                             : maskUsage.opacity,
+                                           maskSurface, maskTransform);
+      } else {
+        context.PushGroupForBlendBack(gfxContentType::COLOR_ALPHA,
+                                      opacityApplied ? 1.0 : maskUsage.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 (maskUsage.shouldApplyClipPath || maskUsage.shouldApplyBasicShape) {
     gfxContextMatrixAutoSaveRestore matSR(&context);
@@ -941,17 +995,17 @@ nsSVGIntegrationUtils::PaintMaskAndClipP
     context.SetColor(Color(0.0, 1.0, 0.0, 1.0));
     context.Fill();
   }
 
   if (maskUsage.shouldApplyClipPath || maskUsage.shouldApplyBasicShape) {
     context.PopClip();
   }
 
-  if (shouldGenerateMask) {
+  if (shouldPushMask) {
     context.PopGroupAndBlend();
   }
 
   return result;
 }
 
 DrawResult
 nsSVGIntegrationUtils::PaintFilter(const PaintFramesParams& aParams)
--- a/layout/svg/nsSVGUtils.cpp
+++ b/layout/svg/nsSVGUtils.cpp
@@ -510,28 +510,17 @@ nsSVGUtils::DetermineMaskUsage(nsIFrame*
 
   nsSVGEffects::EffectProperties effectProperties =
     nsSVGEffects::GetEffectProperties(firstFrame);
   const nsStyleSVGReset *svgReset = firstFrame->StyleSVGReset();
 
   nsTArray<nsSVGMaskFrame*> maskFrames = effectProperties.GetMaskFrames();
 
 #ifdef MOZ_ENABLE_MASK_AS_SHORTHAND
-  // For a HTML doc:
-  //   According to css-masking spec, always create a mask surface when we
-  //   have any item in maskFrame even if all of those items are
-  //   non-resolvable <mask-sources> or <images>, we still need to create a
-  //   transparent black mask layer under this condition.
-  // For a SVG doc:
-  //   SVG 1.1 say that  if we fail to resolve a mask, we should draw the
-  //   object unmasked.
-  aUsage.shouldGenerateMaskLayer =
-    (aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT)
-      ? maskFrames.Length() == 1 && maskFrames[0]
-      : maskFrames.Length() > 0;
+  aUsage.shouldGenerateMaskLayer = (maskFrames.Length() > 0);
 #else
   // Since we do not support image mask so far, we should treat any
   // unresolvable mask as no mask. Otherwise, any object with a valid image
   // mask, e.g. url("xxx.png"), will become invisible just because we can not
   // handle image mask correctly. (See bug 1294171)
   aUsage.shouldGenerateMaskLayer = maskFrames.Length() == 1 && maskFrames[0];
 #endif