Bug 1276834 - Draw nothing when any mask-image refer to an unresolvable URL draft
authorcku <cku@mozilla.com>
Thu, 02 Jun 2016 16:33:54 +0800
changeset 374386 c9359d16e722ba7d4c29791911e1dcbd4240650b
parent 373936 22047a4eea784c15026c77911c0bd6ea1b70fa68
child 522618 078877a8a0562677e48d7841e54e274e86cfe50e
push id20003
push usercku@mozilla.com
push dateThu, 02 Jun 2016 08:54:31 +0000
bugs1276834
milestone49.0a1
Bug 1276834 - Draw nothing when any mask-image refer to an unresolvable URL MozReview-Commit-ID: Ej4uswIIDvS
layout/svg/nsSVGIntegrationUtils.cpp
--- a/layout/svg/nsSVGIntegrationUtils.cpp
+++ b/layout/svg/nsSVGIntegrationUtils.cpp
@@ -421,32 +421,32 @@ GenerateMaskSurface(const nsSVGIntegrati
 
   gfxMatrix cssPxToDevPxMatrix =
     nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(aParams.frame);
 
   gfxContext& ctx = aParams.ctx;
 
   // There is only one SVG mask.
   if (((aMaskFrames.Length() == 1) && aMaskFrames[0])) {
-    aOutMaskSurface =
+    RefPtr<SourceSurface> maskSurface =
       aMaskFrames[0]->GetMaskForMaskedFrame(&ctx, aParams.frame,
                                             cssPxToDevPxMatrix, aOpacity,
                                             &aOutMaskTransform,
                                             svgReset->mMask.mLayers[0].mMaskMode);
-    return;
+    return maskSurface.forget();
   }
 
   ctx.Save();
   ctx.SetMatrix(gfxMatrix());
   gfxRect clipExtents = ctx.GetClipExtents();
   IntRect maskSurfaceRect = RoundedOut(ToRect(clipExtents));
   ctx.Restore();
 
   if (maskSurfaceRect.IsEmpty()) {
-    return;
+    return nullptr;
   }
 
 
   // Mask composition result on CoreGraphic::A8 surface is not correct
   // when mask-mode is not add(source over). Switch to skia when CG backend
   // detected.
   RefPtr<DrawTarget> maskDT =
     (ctx.GetDrawTarget()->GetBackendType() == BackendType::COREGRAPHICS ||
@@ -507,20 +507,53 @@ GenerateMaskSurface(const nsSVGIntegrati
       // FIXME We should use the return value, see bug 1258510.
       Unused << nsCSSRendering::PaintBackgroundWithSC(params, aSC,
                                                       *aParams.frame->StyleBorder());
     }
   }
 
   aOutMaskTransform = ToMatrix(maskSurfaceMatrix);
   if (!aOutMaskTransform.Invert()) {
-    return;
+    return nullptr;
   }
 
-  aOutMaskSurface = maskDT->Snapshot();
+  RefPtr<SourceSurface> maskSurface = maskDT->Snapshot();
+  return maskSurface.forget();
+}
+
+static bool
+ShouldCreateMaskSurface(const nsIFrame* aFrame,
+                        const nsStyleSVGReset* aSVGReset,
+                        const nsTArray<nsSVGMaskFrame *>& maskFrames,
+                        bool& aHasUnresovlableMaskLayer)
+{
+  // 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.
+  nsIDocument* currentDoc = aFrame->PresContext()->Document();
+  bool shouldCreateMaskSurface = currentDoc->IsSVGDocument()
+                                 ? maskFrames.Length() == 1 && maskFrames[0]
+                                 : maskFrames.Length() > 0;
+
+  aHasUnresovlableMaskLayer = false;
+  for (int i = maskFrames.Length() - 1; i >= 0 ; i--) {
+    nsSVGMaskFrame *maskFrame = maskFrames[i];
+
+    if (!maskFrame && !aSVGReset->mMask.mLayers[i].mImage.IsCompleted()) {
+      aHasUnresovlableMaskLayer = true;
+      break;
+    }
+  }
+
+  return shouldCreateMaskSurface;
 }
 
 void
 nsSVGIntegrationUtils::PaintFramesWithEffects(const PaintFramesParams& aParams)
 {
 #ifdef DEBUG
   NS_ASSERTION(!(aParams.frame->GetStateBits() & NS_FRAME_SVG_LAYOUT) ||
                (NS_SVGDisplayListPaintingEnabled() &&
@@ -612,60 +645,68 @@ nsSVGIntegrationUtils::PaintFramesWithEf
     nsLayoutUtils::PointToGfxPoint(offsetToUserSpace,
                                    frame->PresContext()->AppUnitsPerDevPixel());
   context.SetMatrix(context.CurrentMatrix().Translate(devPixelOffsetToUserSpace));
 
   gfxMatrix cssPxToDevPxMatrix = GetCSSPxToDevPxMatrix(frame);
 
   const nsStyleSVGReset *svgReset = firstFrame->StyleSVGReset();
   nsTArray<nsSVGMaskFrame *> maskFrames = effectProperties.GetMaskFrames();
-  // 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.
-  nsIDocument* currentDoc = frame->PresContext()->Document();
-  bool shouldGenerateMaskLayer = currentDoc->IsSVGDocument()
-                                 ? maskFrames.Length() == 1 && maskFrames[0]
-                                 : maskFrames.Length() > 0;
+  bool shouldCreateMaskSurface = false, hasUnresovlableMaskLayer = false;
+  shouldCreateMaskSurface = ShouldCreateMaskSurface(frame, svgReset,
+                                                    maskFrames,
+                                                    hasUnresovlableMaskLayer);
+
+  if (shouldCreateMaskSurface && hasUnresovlableMaskLayer) {
+    // We have at least one mask layer not resolvable. Clear clipped
+    // region as if we push a transparent black mask into context.
+    //
+    // While getting an unresolvable mask layer, you do not  need to care
+    // about any other SVG effects, since a transparent mask basically clean
+    // out everything.
+    gfxContextAutoSaveRestore saver(&context);
+
+    context.SetColor(Color(0.f, 0.f, 0.f, 0.f));
+    context.Paint();
+    return;
+  }
 
   // These are used if we require a temporary surface for a custom blend mode.
   RefPtr<gfxContext> target = &aParams.ctx;
   IntPoint targetOffset;
 
   bool complexEffects = false;
   /* Check if we need to do additional operations on this child's
    * rendering, which necessitates rendering into another surface. */
   if (opacity != 1.0f ||  (clipPathFrame && !isTrivialClip)
       || frame->StyleEffects()->mMixBlendMode != NS_STYLE_BLEND_NORMAL
-      || shouldGenerateMaskLayer) {
+      || shouldCreateMaskSurface) {
     complexEffects = true;
 
     context.Save();
     nsRect clipRect =
       frame->GetVisualOverflowRectRelativeToSelf() + toUserSpace;
     context.Clip(NSRectToSnappedRect(clipRect,
                                   frame->PresContext()->AppUnitsPerDevPixel(),
                                   *drawTarget));
     Matrix maskTransform;
     RefPtr<SourceSurface> maskSurface;
 
-    if (shouldGenerateMaskLayer) {
-      GenerateMaskSurface(aParams, opacity, firstFrame->StyleContext(),
-                          maskFrames, devPixelOffsetToUserSpace,
-                          maskTransform, maskSurface);
-    }
-
-    if (shouldGenerateMaskLayer && !maskSurface) {
-      // Entire surface is clipped out.
-      context.Restore();
-      return;
+    if (shouldCreateMaskSurface) {
+      MOZ_ASSERT(!hasUnresovlableMaskLayer);
+      maskSurface = GenerateMaskSurface(aParams, opacity,
+                                        firstFrame->StyleContext(),
+                                        maskFrames,
+                                        devPixelOffsetToUserSpace,
+                                        maskTransform);
+      if (!maskSurface) {
+        // Entire surface is clipped out.
+        context.Restore();
+        return;
+      }
     }
 
     if (frame->StyleEffects()->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(&context);
@@ -693,17 +734,17 @@ nsSVGIntegrationUtils::PaintFramesWithEf
                                                                          &clippedMaskTransform, maskSurface, maskTransform);
 
       if (clipMaskSurface) {
         maskSurface = clipMaskSurface;
         maskTransform = clippedMaskTransform;
       }
     }
 
-    if (opacity != 1.0f || shouldGenerateMaskLayer ||
+    if (opacity != 1.0f || shouldCreateMaskSurface ||
         (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.
    */
@@ -739,17 +780,17 @@ nsSVGIntegrationUtils::PaintFramesWithEf
     context.Restore();
   }
 
   /* No more effects, we're done. */
   if (!complexEffects) {
     return;
   }
 
-  if (opacity != 1.0f || shouldGenerateMaskLayer ||
+  if (opacity != 1.0f || shouldCreateMaskSurface ||
       (clipPathFrame && !isTrivialClip)) {
     target->PopGroupAndBlend();
   }
 
   if (frame->StyleEffects()->mMixBlendMode != NS_STYLE_BLEND_NORMAL) {
     RefPtr<DrawTarget> targetDT = target->GetDrawTarget();
     target = nullptr;
     RefPtr<SourceSurface> targetSurf = targetDT->Snapshot();