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
--- 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);
/**