Bug 1461313 - Handle invalid clip-path URIs with WebRender. r?mstange draft
authorKartikaya Gupta <kgupta@mozilla.com>
Thu, 05 Jul 2018 08:05:34 -0400
changeset 814467 f98e638e6f1bdfecc295e5611e6575421127845e
parent 814462 085cdfb90903d4985f0de1dc7786522d9fb45596
push id115217
push userkgupta@mozilla.com
push dateThu, 05 Jul 2018 12:06:07 +0000
reviewersmstange
bugs1461313
milestone63.0a1
Bug 1461313 - Handle invalid clip-path URIs with WebRender. r?mstange In the case of an invalid clip-path, the browser is supposed to discard the mask entirely. In the non-webrender codepath this would happen implicitly because the computed MaskUsage would have no flags set, and so no actions would be taken on the gfxContext which contained the display items rasterized so far. In the WebRender codepath, though, we invoke the code on a A8 drawtarget that's zero-filled, so if PaintMask fails to rasterize anything into it, it gets treated as a "mask everything out" mask. Instead, this patch makes it so that we detect the scenario where the computed MaskUsage is a no-op, and ensure that we don't apply the mask in that case. An alternative approach considered was to initialize the A8 drawtarget to white instead of black but in cases where there is an actual mask, the rest of the code assumes it is zero-filled and so that doesn't work. MozReview-Commit-ID: Hw7nCiUXVJl
gfx/layers/wr/WebRenderCommandBuilder.cpp
gfx/tests/reftest/1461313-ref.html
gfx/tests/reftest/1461313.html
gfx/tests/reftest/reftest.list
layout/painting/nsDisplayList.cpp
layout/painting/nsDisplayList.h
layout/svg/nsSVGIntegrationUtils.cpp
layout/svg/nsSVGIntegrationUtils.h
layout/svg/nsSVGUtils.h
--- a/gfx/layers/wr/WebRenderCommandBuilder.cpp
+++ b/gfx/layers/wr/WebRenderCommandBuilder.cpp
@@ -1538,18 +1538,17 @@ PaintItemByDrawTarget(nsDisplayItem* aIt
   bool isInvalidated = false;
   aDT->ClearRect(aImageRect.ToUnknownRect());
   RefPtr<gfxContext> context = gfxContext::CreateOrNull(aDT);
   MOZ_ASSERT(context);
 
   switch (aItem->GetType()) {
   case DisplayItemType::TYPE_MASK:
     context->SetMatrix(context->CurrentMatrix().PreScale(aScale.width, aScale.height).PreTranslate(-aOffset.x, -aOffset.y));
-    static_cast<nsDisplayMask*>(aItem)->PaintMask(aDisplayListBuilder, context);
-    isInvalidated = true;
+    static_cast<nsDisplayMask*>(aItem)->PaintMask(aDisplayListBuilder, context, &isInvalidated);
     break;
   case DisplayItemType::TYPE_SVG_WRAPPER:
     {
       context->SetMatrix(context->CurrentMatrix().PreTranslate(-aOffset.x, -aOffset.y));
       isInvalidated = PaintByLayer(aItem, aDisplayListBuilder, aManager, context, aScale, [&]() {
         aManager->EndTransaction(FrameLayerBuilder::DrawPaintedLayer, aDisplayListBuilder);
       });
       break;
new file mode 100644
--- /dev/null
+++ b/gfx/tests/reftest/1461313-ref.html
@@ -0,0 +1,4 @@
+<!doctype html>
+<body>
+    <div style="background-color: green; width: 100px; height: 100px;"></div>
+</body>
new file mode 100644
--- /dev/null
+++ b/gfx/tests/reftest/1461313.html
@@ -0,0 +1,9 @@
+<!doctype html>
+<style>
+body {
+  clip-path: url(non-existent-resource);
+}
+</style>
+<body>
+    <div style="background-color: green; width: 100px; height: 100px;"></div>
+</body>
--- a/gfx/tests/reftest/reftest.list
+++ b/gfx/tests/reftest/reftest.list
@@ -9,8 +9,9 @@ fuzzy(100,30) == 1149923.html 1149923-re
 == 1131264-1.svg pass.svg
 == 1419528.html 1419528-ref.html
 == 1424673.html 1424673-ref.html
 == 1429411.html 1429411-ref.html
 == 1435143.html 1435143-ref.html
 == 1444904.html 1444904-ref.html
 == 1451168.html 1451168-ref.html
 fuzzy(5-32,21908-26354) fuzzy-if(webrender,0-1,0-3) == 1463802.html 1463802-ref.html
+== 1461313.html 1461313-ref.html
--- a/layout/painting/nsDisplayList.cpp
+++ b/layout/painting/nsDisplayList.cpp
@@ -9195,31 +9195,35 @@ nsDisplayMask::BuildLayer(nsDisplayListB
     BuildContainerLayerFor(aBuilder, aManager, mFrame, this, &mList,
                            aContainerParameters, nullptr);
 
   return container.forget();
 }
 
 bool
 nsDisplayMask::PaintMask(nsDisplayListBuilder* aBuilder,
-                         gfxContext* aMaskContext)
+                         gfxContext* aMaskContext,
+                         bool* aMaskPainted)
 {
   MOZ_ASSERT(aMaskContext->GetDrawTarget()->GetFormat() == SurfaceFormat::A8);
 
   imgDrawingParams imgParmas(aBuilder->ShouldSyncDecodeImages()
                              ? imgIContainer::FLAG_SYNC_DECODE
                              : imgIContainer::FLAG_SYNC_DECODE_IF_FAST);
   nsRect borderArea = nsRect(ToReferenceFrame(), mFrame->GetSize());
   nsSVGIntegrationUtils::PaintFramesParams params(*aMaskContext,
                                                   mFrame,  GetBuildingRect(),
                                                   borderArea, aBuilder,
                                                   nullptr,
                                                   mHandleOpacity, imgParmas);
   ComputeMaskGeometry(params);
-  nsSVGIntegrationUtils::PaintMask(params);
+  bool painted = nsSVGIntegrationUtils::PaintMask(params);
+  if (aMaskPainted) {
+    *aMaskPainted = painted;
+  }
 
   nsDisplayMaskGeometry::UpdateDrawResult(this, imgParmas.result);
 
   return imgParmas.result == mozilla::image::ImgDrawResult::SUCCESS;
 }
 
 LayerState
 nsDisplayMask::GetLayerState(nsDisplayListBuilder* aBuilder,
--- a/layout/painting/nsDisplayList.h
+++ b/layout/painting/nsDisplayList.h
@@ -6088,17 +6088,19 @@ public:
   void PaintAsLayer(nsDisplayListBuilder* aBuilder,
                     gfxContext* aCtx,
                     LayerManager* aManager);
 
   /*
    * Paint mask onto aMaskContext in mFrame's coordinate space and
    * return whether the mask layer was painted successfully.
    */
-  bool PaintMask(nsDisplayListBuilder* aBuilder, gfxContext* aMaskContext);
+  bool PaintMask(nsDisplayListBuilder* aBuilder,
+                 gfxContext* aMaskContext,
+                 bool* aMaskPainted = nullptr);
 
   const nsTArray<nsRect>& GetDestRects()
   {
     return mDestRects;
   }
 
   virtual bool CreateWebRenderCommands(mozilla::wr::DisplayListBuilder& aBuilder,
                                        mozilla::wr::IpcResourceUpdateQueue& aResources,
--- a/layout/svg/nsSVGIntegrationUtils.cpp
+++ b/layout/svg/nsSVGIntegrationUtils.cpp
@@ -588,17 +588,17 @@ CreateAndPaintMaskSurface(const PaintFra
     //   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
+    //   Left paintResult.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;
   }
 
@@ -755,26 +755,29 @@ public:
   void SetContext(gfxContext* aContext) {
     mContext = aContext;
   }
 
 private:
   gfxContext* mContext;
 };
 
-void
+bool
 nsSVGIntegrationUtils::PaintMask(const PaintFramesParams& aParams)
 {
   nsSVGUtils::MaskUsage maskUsage;
   nsSVGUtils::DetermineMaskUsage(aParams.frame, aParams.handleOpacity,
                                  maskUsage);
+  if (!maskUsage.shouldDoSomething()) {
+    return false;
+  }
 
   nsIFrame* frame = aParams.frame;
   if (!ValidateSVGFrame(frame)) {
-    return;
+    return false;
   }
 
   gfxContext& ctx = aParams.ctx;
   nsIFrame* firstFrame =
     nsLayoutUtils::FirstContinuationOrIBSplitSibling(frame);
   SVGObserverUtils::EffectProperties effectProperties =
     SVGObserverUtils::GetEffectProperties(firstFrame);
 
@@ -814,17 +817,17 @@ nsSVGIntegrationUtils::PaintMask(const P
     basicShapeSR.SetContext(&ctx);
     nsCSSClipPathInstance::ApplyBasicShapeClip(ctx, frame);
     if (!maskUsage.shouldGenerateMaskLayer) {
       // Only have basic-shape clip-path effect. Fill clipped region by
       // opaque white.
       ctx.SetColor(Color(1.0, 1.0, 1.0, 1.0));
       ctx.Fill();
 
-      return;
+      return true;
     }
   }
 
   // Paint mask onto ctx.
   if (maskUsage.shouldGenerateMaskLayer) {
     matSR.Restore();
     matSR.SetContext(&ctx);
 
@@ -847,16 +850,18 @@ nsSVGIntegrationUtils::PaintMask(const P
 
     nsSVGClipPathFrame *clipPathFrame = effectProperties.GetClipPathFrame();
     RefPtr<SourceSurface> maskSurface =
       maskUsage.shouldGenerateMaskLayer ? maskTarget->Snapshot() : nullptr;
     clipPathFrame->PaintClipMask(ctx, frame, cssPxToDevPxMatrix,
                                    &clipMaskTransform, maskSurface,
                                    ctx.CurrentMatrix());
   }
+
+  return true;
 }
 
 void
 nsSVGIntegrationUtils::PaintMaskAndClipPath(const PaintFramesParams& aParams)
 {
   MOZ_ASSERT(UsingMaskOrClipPathForFrame(aParams.frame),
              "Should not use this method when no mask or clipPath effect"
              "on this frame");
--- a/layout/svg/nsSVGIntegrationUtils.h
+++ b/layout/svg/nsSVGIntegrationUtils.h
@@ -163,19 +163,20 @@ public:
   /**
    * Paint non-SVG frame with mask, clipPath and opacity effect.
    */
   static void
   PaintMaskAndClipPath(const PaintFramesParams& aParams);
 
   /**
    * Paint mask of non-SVG frame onto a given context, aParams.ctx.
-   * aParams.ctx must contain an A8 surface.
+   * aParams.ctx must contain an A8 surface. Returns false if the mask
+   * didn't get painted and should be ignored at the call site.
    */
-  static void
+  static bool
   PaintMask(const PaintFramesParams& aParams);
 
   /**
    * Return true if all the mask resource of aFrame are ready.
    */
   static bool
   IsMaskResourceReady(nsIFrame* aFrame);
 
--- a/layout/svg/nsSVGUtils.h
+++ b/layout/svg/nsSVGUtils.h
@@ -608,16 +608,24 @@ public:
     bool shouldApplyClipPath;
     bool shouldApplyBasicShape;
     float opacity;
 
     MaskUsage()
       : shouldGenerateMaskLayer(false), shouldGenerateClipMaskLayer(false),
         shouldApplyClipPath(false), shouldApplyBasicShape(false), opacity(0.0)
     { }
+
+    bool shouldDoSomething() {
+      return shouldGenerateMaskLayer
+          || shouldGenerateClipMaskLayer
+          || shouldApplyClipPath
+          || shouldApplyBasicShape
+          || opacity != 1.0;
+    }
   };
 
   static void DetermineMaskUsage(nsIFrame* aFrame, bool aHandleOpacity,
                                  MaskUsage& aUsage);
 
   static float ComputeOpacity(nsIFrame* aFrame, bool aHandleOpacity);
 
   /**