1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 // Main header first:
8 #include "nsSVGClipPathFrame.h"
9 
10 // Keep others in (case-insensitive) order:
11 #include "AutoReferenceChainGuard.h"
12 #include "ImgDrawResult.h"
13 #include "gfxContext.h"
14 #include "mozilla/PresShell.h"
15 #include "mozilla/dom/SVGClipPathElement.h"
16 #include "nsGkAtoms.h"
17 #include "SVGObserverUtils.h"
18 #include "SVGGeometryElement.h"
19 #include "SVGGeometryFrame.h"
20 #include "nsSVGUtils.h"
21 
22 using namespace mozilla;
23 using namespace mozilla::dom;
24 using namespace mozilla::gfx;
25 using namespace mozilla::image;
26 
27 //----------------------------------------------------------------------
28 // Implementation
29 
NS_NewSVGClipPathFrame(PresShell * aPresShell,ComputedStyle * aStyle)30 nsIFrame* NS_NewSVGClipPathFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
31   return new (aPresShell)
32       nsSVGClipPathFrame(aStyle, aPresShell->GetPresContext());
33 }
34 
NS_IMPL_FRAMEARENA_HELPERS(nsSVGClipPathFrame)35 NS_IMPL_FRAMEARENA_HELPERS(nsSVGClipPathFrame)
36 
37 void nsSVGClipPathFrame::ApplyClipPath(gfxContext& aContext,
38                                        nsIFrame* aClippedFrame,
39                                        const gfxMatrix& aMatrix) {
40   MOZ_ASSERT(IsTrivial(), "Caller needs to use GetClipMask");
41 
42   const DrawTarget* drawTarget = aContext.GetDrawTarget();
43 
44   // No need for AutoReferenceChainGuard since simple clip paths by definition
45   // don't reference another clip path.
46 
47   // Restore current transform after applying clip path:
48   gfxContextMatrixAutoSaveRestore autoRestore(&aContext);
49 
50   RefPtr<Path> clipPath;
51 
52   nsSVGDisplayableFrame* singleClipPathChild = nullptr;
53   IsTrivial(&singleClipPathChild);
54 
55   if (singleClipPathChild) {
56     SVGGeometryFrame* pathFrame = do_QueryFrame(singleClipPathChild);
57     if (pathFrame && pathFrame->StyleVisibility()->IsVisible()) {
58       SVGGeometryElement* pathElement =
59           static_cast<SVGGeometryElement*>(pathFrame->GetContent());
60 
61       gfxMatrix toChildsUserSpace =
62           nsSVGUtils::GetTransformMatrixInUserSpace(pathFrame) *
63           (GetClipPathTransform(aClippedFrame) * aMatrix);
64 
65       gfxMatrix newMatrix = aContext.CurrentMatrixDouble()
66                                 .PreMultiply(toChildsUserSpace)
67                                 .NudgeToIntegers();
68       if (!newMatrix.IsSingular()) {
69         aContext.SetMatrixDouble(newMatrix);
70         FillRule clipRule =
71             nsSVGUtils::ToFillRule(pathFrame->StyleSVG()->mClipRule);
72         clipPath = pathElement->GetOrBuildPath(drawTarget, clipRule);
73       }
74     }
75   }
76 
77   if (clipPath) {
78     aContext.Clip(clipPath);
79   } else {
80     // The spec says clip away everything if we have no children or the
81     // clipping path otherwise can't be resolved:
82     aContext.Clip(Rect());
83   }
84 }
85 
ComposeExtraMask(DrawTarget * aTarget,SourceSurface * aExtraMask,const Matrix & aExtraMasksTransform)86 static void ComposeExtraMask(DrawTarget* aTarget, SourceSurface* aExtraMask,
87                              const Matrix& aExtraMasksTransform) {
88   MOZ_ASSERT(aExtraMask);
89 
90   Matrix origin = aTarget->GetTransform();
91   aTarget->SetTransform(aExtraMasksTransform * aTarget->GetTransform());
92   aTarget->MaskSurface(ColorPattern(DeviceColor(0.0, 0.0, 0.0, 1.0)),
93                        aExtraMask, Point(0, 0),
94                        DrawOptions(1.0, CompositionOp::OP_IN));
95   aTarget->SetTransform(origin);
96 }
97 
PaintClipMask(gfxContext & aMaskContext,nsIFrame * aClippedFrame,const gfxMatrix & aMatrix,SourceSurface * aExtraMask,const Matrix & aExtraMasksTransform)98 void nsSVGClipPathFrame::PaintClipMask(gfxContext& aMaskContext,
99                                        nsIFrame* aClippedFrame,
100                                        const gfxMatrix& aMatrix,
101                                        SourceSurface* aExtraMask,
102                                        const Matrix& aExtraMasksTransform) {
103   static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain;
104 
105   // A clipPath can reference another clipPath, creating a chain of clipPaths
106   // that must all be applied.  We re-enter this method for each clipPath in a
107   // chain, so we need to protect against reference chain related crashes etc.:
108   AutoReferenceChainGuard refChainGuard(this, &mIsBeingProcessed,
109                                         &sRefChainLengthCounter);
110   if (MOZ_UNLIKELY(!refChainGuard.Reference())) {
111     return;  // Break reference chain
112   }
113 
114   DrawTarget* maskDT = aMaskContext.GetDrawTarget();
115   MOZ_ASSERT(maskDT->GetFormat() == SurfaceFormat::A8);
116 
117   // Paint this clipPath's contents into aMaskDT:
118   // We need to set mMatrixForChildren here so that under the PaintSVG calls
119   // on our children (below) our GetCanvasTM() method will return the correct
120   // transform.
121   mMatrixForChildren = GetClipPathTransform(aClippedFrame) * aMatrix;
122 
123   // Check if this clipPath is itself clipped by another clipPath:
124   nsSVGClipPathFrame* clipPathThatClipsClipPath;
125   // XXX check return value?
126   SVGObserverUtils::GetAndObserveClipPath(this, &clipPathThatClipsClipPath);
127   nsSVGUtils::MaskUsage maskUsage;
128   nsSVGUtils::DetermineMaskUsage(this, true, maskUsage);
129 
130   if (maskUsage.shouldApplyClipPath) {
131     clipPathThatClipsClipPath->ApplyClipPath(aMaskContext, aClippedFrame,
132                                              aMatrix);
133   } else if (maskUsage.shouldGenerateClipMaskLayer) {
134     RefPtr<SourceSurface> maskSurface = clipPathThatClipsClipPath->GetClipMask(
135         aMaskContext, aClippedFrame, aMatrix);
136     // We want the mask to be untransformed so use the inverse of the current
137     // transform as the maskTransform to compensate.
138     Matrix maskTransform = aMaskContext.CurrentMatrix();
139     maskTransform.Invert();
140     aMaskContext.PushGroupForBlendBack(gfxContentType::ALPHA, 1.0, maskSurface,
141                                        maskTransform);
142     // The corresponding PopGroupAndBlend call below will mask the
143     // blend using |maskSurface|.
144   }
145 
146   // Paint our children into the mask:
147   for (nsIFrame* kid = mFrames.FirstChild(); kid; kid = kid->GetNextSibling()) {
148     PaintFrameIntoMask(kid, aClippedFrame, aMaskContext);
149   }
150 
151   if (maskUsage.shouldGenerateClipMaskLayer) {
152     aMaskContext.PopGroupAndBlend();
153   } else if (maskUsage.shouldApplyClipPath) {
154     aMaskContext.PopClip();
155   }
156 
157   if (aExtraMask) {
158     ComposeExtraMask(maskDT, aExtraMask, aExtraMasksTransform);
159   }
160 }
161 
PaintFrameIntoMask(nsIFrame * aFrame,nsIFrame * aClippedFrame,gfxContext & aTarget)162 void nsSVGClipPathFrame::PaintFrameIntoMask(nsIFrame* aFrame,
163                                             nsIFrame* aClippedFrame,
164                                             gfxContext& aTarget) {
165   nsSVGDisplayableFrame* frame = do_QueryFrame(aFrame);
166   if (!frame) {
167     return;
168   }
169 
170   // The CTM of each frame referencing us can be different.
171   frame->NotifySVGChanged(nsSVGDisplayableFrame::TRANSFORM_CHANGED);
172 
173   // Children of this clipPath may themselves be clipped.
174   nsSVGClipPathFrame* clipPathThatClipsChild;
175   // XXX check return value?
176   if (SVGObserverUtils::GetAndObserveClipPath(aFrame,
177                                               &clipPathThatClipsChild) ==
178       SVGObserverUtils::eHasRefsSomeInvalid) {
179     return;
180   }
181 
182   nsSVGUtils::MaskUsage maskUsage;
183   nsSVGUtils::DetermineMaskUsage(aFrame, true, maskUsage);
184   if (maskUsage.shouldApplyClipPath) {
185     clipPathThatClipsChild->ApplyClipPath(aTarget, aClippedFrame,
186                                           mMatrixForChildren);
187   } else if (maskUsage.shouldGenerateClipMaskLayer) {
188     RefPtr<SourceSurface> maskSurface = clipPathThatClipsChild->GetClipMask(
189         aTarget, aClippedFrame, mMatrixForChildren);
190 
191     // We want the mask to be untransformed so use the inverse of the current
192     // transform as the maskTransform to compensate.
193     Matrix maskTransform = aTarget.CurrentMatrix();
194     maskTransform.Invert();
195     aTarget.PushGroupForBlendBack(gfxContentType::ALPHA, 1.0, maskSurface,
196                                   maskTransform);
197     // The corresponding PopGroupAndBlend call below will mask the
198     // blend using |maskSurface|.
199   }
200 
201   gfxMatrix toChildsUserSpace = mMatrixForChildren;
202   nsIFrame* child = do_QueryFrame(frame);
203   nsIContent* childContent = child->GetContent();
204   if (childContent->IsSVGElement()) {
205     toChildsUserSpace =
206         nsSVGUtils::GetTransformMatrixInUserSpace(child) * mMatrixForChildren;
207   }
208 
209   // clipPath does not result in any image rendering, so we just use a dummy
210   // imgDrawingParams instead of requiring our caller to pass one.
211   image::imgDrawingParams imgParams;
212 
213   // Our children have NS_STATE_SVG_CLIPPATH_CHILD set on them, and
214   // SVGGeometryFrame::Render checks for that state bit and paints
215   // only the geometry (opaque black) if set.
216   frame->PaintSVG(aTarget, toChildsUserSpace, imgParams);
217 
218   if (maskUsage.shouldGenerateClipMaskLayer) {
219     aTarget.PopGroupAndBlend();
220   } else if (maskUsage.shouldApplyClipPath) {
221     aTarget.PopClip();
222   }
223 }
224 
GetClipMask(gfxContext & aReferenceContext,nsIFrame * aClippedFrame,const gfxMatrix & aMatrix,SourceSurface * aExtraMask,const Matrix & aExtraMasksTransform)225 already_AddRefed<SourceSurface> nsSVGClipPathFrame::GetClipMask(
226     gfxContext& aReferenceContext, nsIFrame* aClippedFrame,
227     const gfxMatrix& aMatrix, SourceSurface* aExtraMask,
228     const Matrix& aExtraMasksTransform) {
229   RefPtr<DrawTarget> maskDT =
230       aReferenceContext.GetDrawTarget()->CreateClippedDrawTarget(
231           Rect(), SurfaceFormat::A8);
232   if (!maskDT) {
233     return nullptr;
234   }
235 
236   RefPtr<gfxContext> maskContext =
237       gfxContext::CreatePreservingTransformOrNull(maskDT);
238   if (!maskContext) {
239     gfxCriticalError() << "SVGClipPath context problem " << gfx::hexa(maskDT);
240     return nullptr;
241   }
242 
243   PaintClipMask(*maskContext, aClippedFrame, aMatrix, aExtraMask,
244                 aExtraMasksTransform);
245 
246   RefPtr<SourceSurface> surface = maskDT->Snapshot();
247   return surface.forget();
248 }
249 
PointIsInsideClipPath(nsIFrame * aClippedFrame,const gfxPoint & aPoint)250 bool nsSVGClipPathFrame::PointIsInsideClipPath(nsIFrame* aClippedFrame,
251                                                const gfxPoint& aPoint) {
252   static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain;
253 
254   // A clipPath can reference another clipPath, creating a chain of clipPaths
255   // that must all be applied.  We re-enter this method for each clipPath in a
256   // chain, so we need to protect against reference chain related crashes etc.:
257   AutoReferenceChainGuard refChainGuard(this, &mIsBeingProcessed,
258                                         &sRefChainLengthCounter);
259   if (MOZ_UNLIKELY(!refChainGuard.Reference())) {
260     return false;  // Break reference chain
261   }
262 
263   gfxMatrix matrix = GetClipPathTransform(aClippedFrame);
264   if (!matrix.Invert()) {
265     return false;
266   }
267   gfxPoint point = matrix.TransformPoint(aPoint);
268 
269   // clipPath elements can themselves be clipped by a different clip path. In
270   // that case the other clip path further clips away the element that is being
271   // clipped by the original clipPath. If this clipPath is being clipped by a
272   // different clip path we need to check if it prevents the original element
273   // from receiving events at aPoint:
274   nsSVGClipPathFrame* clipPathFrame;
275   // XXX check return value?
276   SVGObserverUtils::GetAndObserveClipPath(this, &clipPathFrame);
277   if (clipPathFrame &&
278       !clipPathFrame->PointIsInsideClipPath(aClippedFrame, aPoint)) {
279     return false;
280   }
281 
282   for (nsIFrame* kid = mFrames.FirstChild(); kid; kid = kid->GetNextSibling()) {
283     nsSVGDisplayableFrame* SVGFrame = do_QueryFrame(kid);
284     if (SVGFrame) {
285       gfxPoint pointForChild = point;
286 
287       gfxMatrix m = nsSVGUtils::GetTransformMatrixInUserSpace(kid);
288       if (!m.IsIdentity()) {
289         if (!m.Invert()) {
290           return false;
291         }
292         pointForChild = m.TransformPoint(point);
293       }
294       if (SVGFrame->GetFrameForPoint(pointForChild)) {
295         return true;
296       }
297     }
298   }
299 
300   return false;
301 }
302 
IsTrivial(nsSVGDisplayableFrame ** aSingleChild)303 bool nsSVGClipPathFrame::IsTrivial(nsSVGDisplayableFrame** aSingleChild) {
304   // If the clip path is clipped then it's non-trivial
305   if (SVGObserverUtils::GetAndObserveClipPath(this, nullptr) ==
306       SVGObserverUtils::eHasRefsAllValid) {
307     return false;
308   }
309 
310   if (aSingleChild) {
311     *aSingleChild = nullptr;
312   }
313 
314   nsSVGDisplayableFrame* foundChild = nullptr;
315 
316   for (nsIFrame* kid = mFrames.FirstChild(); kid; kid = kid->GetNextSibling()) {
317     nsSVGDisplayableFrame* svgChild = do_QueryFrame(kid);
318     if (svgChild) {
319       // We consider a non-trivial clipPath to be one containing
320       // either more than one svg child and/or a svg container
321       if (foundChild || svgChild->IsDisplayContainer()) {
322         return false;
323       }
324 
325       // or where the child is itself clipped
326       if (SVGObserverUtils::GetAndObserveClipPath(kid, nullptr) ==
327           SVGObserverUtils::eHasRefsAllValid) {
328         return false;
329       }
330 
331       foundChild = svgChild;
332     }
333   }
334   if (aSingleChild) {
335     *aSingleChild = foundChild;
336   }
337   return true;
338 }
339 
IsValid()340 bool nsSVGClipPathFrame::IsValid() {
341   static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain;
342 
343   // A clipPath can reference another clipPath, creating a chain of clipPaths
344   // that must all be applied.  We re-enter this method for each clipPath in a
345   // chain, so we need to protect against reference chain related crashes etc.:
346   AutoReferenceChainGuard refChainGuard(this, &mIsBeingProcessed,
347                                         &sRefChainLengthCounter);
348   if (MOZ_UNLIKELY(!refChainGuard.Reference())) {
349     return false;  // Break reference chain
350   }
351 
352   if (SVGObserverUtils::GetAndObserveClipPath(this, nullptr) ==
353       SVGObserverUtils::eHasRefsSomeInvalid) {
354     return false;
355   }
356 
357   for (nsIFrame* kid = mFrames.FirstChild(); kid; kid = kid->GetNextSibling()) {
358     LayoutFrameType kidType = kid->Type();
359 
360     if (kidType == LayoutFrameType::SVGUse) {
361       for (nsIFrame* grandKid : kid->PrincipalChildList()) {
362         LayoutFrameType grandKidType = grandKid->Type();
363 
364         if (grandKidType != LayoutFrameType::SVGGeometry &&
365             grandKidType != LayoutFrameType::SVGText) {
366           return false;
367         }
368       }
369       continue;
370     }
371 
372     if (kidType != LayoutFrameType::SVGGeometry &&
373         kidType != LayoutFrameType::SVGText) {
374       return false;
375     }
376   }
377 
378   return true;
379 }
380 
AttributeChanged(int32_t aNameSpaceID,nsAtom * aAttribute,int32_t aModType)381 nsresult nsSVGClipPathFrame::AttributeChanged(int32_t aNameSpaceID,
382                                               nsAtom* aAttribute,
383                                               int32_t aModType) {
384   if (aNameSpaceID == kNameSpaceID_None) {
385     if (aAttribute == nsGkAtoms::transform) {
386       SVGObserverUtils::InvalidateDirectRenderingObservers(this);
387       nsSVGUtils::NotifyChildrenOfSVGChange(
388           this, nsSVGDisplayableFrame::TRANSFORM_CHANGED);
389     }
390     if (aAttribute == nsGkAtoms::clipPathUnits) {
391       SVGObserverUtils::InvalidateDirectRenderingObservers(this);
392     }
393   }
394 
395   return nsSVGContainerFrame::AttributeChanged(aNameSpaceID, aAttribute,
396                                                aModType);
397 }
398 
Init(nsIContent * aContent,nsContainerFrame * aParent,nsIFrame * aPrevInFlow)399 void nsSVGClipPathFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
400                               nsIFrame* aPrevInFlow) {
401   NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::clipPath),
402                "Content is not an SVG clipPath!");
403 
404   AddStateBits(NS_STATE_SVG_CLIPPATH_CHILD);
405   nsSVGContainerFrame::Init(aContent, aParent, aPrevInFlow);
406 }
407 
GetCanvasTM()408 gfxMatrix nsSVGClipPathFrame::GetCanvasTM() { return mMatrixForChildren; }
409 
GetClipPathTransform(nsIFrame * aClippedFrame)410 gfxMatrix nsSVGClipPathFrame::GetClipPathTransform(nsIFrame* aClippedFrame) {
411   SVGClipPathElement* content = static_cast<SVGClipPathElement*>(GetContent());
412 
413   gfxMatrix tm = content->PrependLocalTransformsTo({}, eChildToUserSpace) *
414                  nsSVGUtils::GetTransformMatrixInUserSpace(this);
415 
416   SVGAnimatedEnumeration* clipPathUnits =
417       &content->mEnumAttributes[SVGClipPathElement::CLIPPATHUNITS];
418 
419   uint32_t flags = nsSVGUtils::eBBoxIncludeFillGeometry |
420                    (aClippedFrame->StyleBorder()->mBoxDecorationBreak ==
421                             StyleBoxDecorationBreak::Clone
422                         ? nsSVGUtils::eIncludeOnlyCurrentFrameForNonSVGElement
423                         : 0);
424 
425   return nsSVGUtils::AdjustMatrixForUnits(tm, clipPathUnits, aClippedFrame,
426                                           flags);
427 }
428 
GetBBoxForClipPathFrame(const SVGBBox & aBBox,const gfxMatrix & aMatrix,uint32_t aFlags)429 SVGBBox nsSVGClipPathFrame::GetBBoxForClipPathFrame(const SVGBBox& aBBox,
430                                                     const gfxMatrix& aMatrix,
431                                                     uint32_t aFlags) {
432   nsSVGClipPathFrame* clipPathThatClipsClipPath;
433   if (SVGObserverUtils::GetAndObserveClipPath(this,
434                                               &clipPathThatClipsClipPath) ==
435       SVGObserverUtils::eHasRefsSomeInvalid) {
436     return SVGBBox();
437   }
438 
439   nsIContent* node = GetContent()->GetFirstChild();
440   SVGBBox unionBBox, tmpBBox;
441   for (; node; node = node->GetNextSibling()) {
442     SVGElement* svgNode = static_cast<SVGElement*>(node);
443     nsIFrame* frame = svgNode->GetPrimaryFrame();
444     if (frame) {
445       nsSVGDisplayableFrame* svg = do_QueryFrame(frame);
446       if (svg) {
447         gfxMatrix matrix =
448             nsSVGUtils::GetTransformMatrixInUserSpace(frame) * aMatrix;
449         tmpBBox = svg->GetBBoxContribution(mozilla::gfx::ToMatrix(matrix),
450                                            nsSVGUtils::eBBoxIncludeFill);
451         nsSVGClipPathFrame* clipPathFrame;
452         if (SVGObserverUtils::GetAndObserveClipPath(frame, &clipPathFrame) !=
453                 SVGObserverUtils::eHasRefsSomeInvalid &&
454             clipPathFrame) {
455           tmpBBox =
456               clipPathFrame->GetBBoxForClipPathFrame(tmpBBox, aMatrix, aFlags);
457         }
458         if (!(aFlags & nsSVGUtils::eDoNotClipToBBoxOfContentInsideClipPath)) {
459           tmpBBox.Intersect(aBBox);
460         }
461         unionBBox.UnionEdges(tmpBBox);
462       }
463     }
464   }
465 
466   if (clipPathThatClipsClipPath) {
467     tmpBBox = clipPathThatClipsClipPath->GetBBoxForClipPathFrame(aBBox, aMatrix,
468                                                                  aFlags);
469     unionBBox.Intersect(tmpBBox);
470   }
471   return unionBBox;
472 }
473 
IsSVGTransformed(Matrix * aOwnTransforms,Matrix * aFromParentTransforms) const474 bool nsSVGClipPathFrame::IsSVGTransformed(Matrix* aOwnTransforms,
475                                           Matrix* aFromParentTransforms) const {
476   auto e = static_cast<SVGElement const*>(GetContent());
477   Matrix m = ToMatrix(e->PrependLocalTransformsTo({}, eUserSpaceToParent));
478 
479   if (m.IsIdentity()) {
480     return false;
481   }
482 
483   if (aOwnTransforms) {
484     *aOwnTransforms = m;
485   }
486 
487   return true;
488 }
489