Bumping manifests a=b2g-bump
[gecko.git] / layout / svg / nsSVGClipPathFrame.cpp
blob76df0c59af99ffcf4e57846a915e0542661921a4
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 // Main header first:
7 #include "nsSVGClipPathFrame.h"
9 // Keep others in (case-insensitive) order:
10 #include "gfxContext.h"
11 #include "nsGkAtoms.h"
12 #include "nsRenderingContext.h"
13 #include "mozilla/dom/SVGClipPathElement.h"
14 #include "nsSVGEffects.h"
15 #include "nsSVGUtils.h"
17 using namespace mozilla::dom;
19 //----------------------------------------------------------------------
20 // Implementation
22 nsIFrame*
23 NS_NewSVGClipPathFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
25 return new (aPresShell) nsSVGClipPathFrame(aContext);
28 NS_IMPL_FRAMEARENA_HELPERS(nsSVGClipPathFrame)
30 nsresult
31 nsSVGClipPathFrame::ApplyClipOrPaintClipMask(nsRenderingContext* aContext,
32 nsIFrame* aClippedFrame,
33 const gfxMatrix& aMatrix)
35 // If the flag is set when we get here, it means this clipPath frame
36 // has already been used painting the current clip, and the document
37 // has a clip reference loop.
38 if (mInUse) {
39 NS_WARNING("Clip loop detected!");
40 return NS_OK;
42 AutoClipPathReferencer clipRef(this);
44 mMatrixForChildren = GetClipPathTransform(aClippedFrame) * aMatrix;
46 gfxContext *gfx = aContext->ThebesContext();
48 nsISVGChildFrame *singleClipPathChild = nullptr;
50 if (IsTrivial(&singleClipPathChild)) {
51 // Notify our child that it's painting as part of a clipPath, and that
52 // we only require it to draw its path (it should skip filling, etc.):
53 SVGAutoRenderState mode(aContext, SVGAutoRenderState::CLIP);
55 if (!singleClipPathChild) {
56 // We have no children - the spec says clip away everything:
57 gfx->Rectangle(gfxRect());
58 } else {
59 singleClipPathChild->NotifySVGChanged(
60 nsISVGChildFrame::TRANSFORM_CHANGED);
61 singleClipPathChild->PaintSVG(aContext, nullptr);
63 gfx->Clip();
64 gfx->NewPath();
65 return NS_OK;
68 // Seems like this is a non-trivial clipPath, so we need to use a clip mask.
70 // Notify our children that they're painting into a clip mask:
71 SVGAutoRenderState mode(aContext, SVGAutoRenderState::CLIP_MASK);
73 // Check if this clipPath is itself clipped by another clipPath:
74 nsSVGClipPathFrame *clipPathFrame =
75 nsSVGEffects::GetEffectProperties(this).GetClipPathFrame(nullptr);
76 bool referencedClipIsTrivial;
77 if (clipPathFrame) {
78 referencedClipIsTrivial = clipPathFrame->IsTrivial();
79 gfx->Save();
80 if (referencedClipIsTrivial) {
81 clipPathFrame->ApplyClipOrPaintClipMask(aContext, aClippedFrame, aMatrix);
82 } else {
83 gfx->PushGroup(gfxContentType::ALPHA);
87 for (nsIFrame* kid = mFrames.FirstChild(); kid;
88 kid = kid->GetNextSibling()) {
89 nsISVGChildFrame* SVGFrame = do_QueryFrame(kid);
90 if (SVGFrame) {
91 // The CTM of each frame referencing us can be different.
92 SVGFrame->NotifySVGChanged(nsISVGChildFrame::TRANSFORM_CHANGED);
94 bool isOK = true;
95 nsSVGClipPathFrame *clipPathFrame =
96 nsSVGEffects::GetEffectProperties(kid).GetClipPathFrame(&isOK);
97 if (!isOK) {
98 continue;
101 bool isTrivial;
103 if (clipPathFrame) {
104 isTrivial = clipPathFrame->IsTrivial();
105 gfx->Save();
106 if (isTrivial) {
107 clipPathFrame->ApplyClipOrPaintClipMask(aContext, aClippedFrame, aMatrix);
108 } else {
109 gfx->PushGroup(gfxContentType::ALPHA);
113 SVGFrame->PaintSVG(aContext, nullptr);
115 if (clipPathFrame) {
116 if (!isTrivial) {
117 gfx->PopGroupToSource();
119 nsRefPtr<gfxPattern> clipMaskSurface;
120 gfx->PushGroup(gfxContentType::ALPHA);
122 clipPathFrame->ApplyClipOrPaintClipMask(aContext, aClippedFrame, aMatrix);
123 clipMaskSurface = gfx->PopGroup();
125 if (clipMaskSurface) {
126 gfx->Mask(clipMaskSurface);
129 gfx->Restore();
134 if (clipPathFrame) {
135 if (!referencedClipIsTrivial) {
136 gfx->PopGroupToSource();
138 nsRefPtr<gfxPattern> clipMaskSurface;
139 gfx->PushGroup(gfxContentType::ALPHA);
141 clipPathFrame->ApplyClipOrPaintClipMask(aContext, aClippedFrame, aMatrix);
142 clipMaskSurface = gfx->PopGroup();
144 if (clipMaskSurface) {
145 gfx->Mask(clipMaskSurface);
148 gfx->Restore();
151 return NS_OK;
154 bool
155 nsSVGClipPathFrame::PointIsInsideClipPath(nsIFrame* aClippedFrame,
156 const gfxPoint &aPoint)
158 // If the flag is set when we get here, it means this clipPath frame
159 // has already been used in hit testing against the current clip,
160 // and the document has a clip reference loop.
161 if (mInUse) {
162 NS_WARNING("Clip loop detected!");
163 return false;
165 AutoClipPathReferencer clipRef(this);
167 gfxMatrix matrix = GetClipPathTransform(aClippedFrame);
168 if (!matrix.Invert()) {
169 return false;
171 gfxPoint point = matrix.Transform(aPoint);
173 // clipPath elements can themselves be clipped by a different clip path. In
174 // that case the other clip path further clips away the element that is being
175 // clipped by the original clipPath. If this clipPath is being clipped by a
176 // different clip path we need to check if it prevents the original element
177 // from recieving events at aPoint:
178 nsSVGClipPathFrame *clipPathFrame =
179 nsSVGEffects::GetEffectProperties(this).GetClipPathFrame(nullptr);
180 if (clipPathFrame &&
181 !clipPathFrame->PointIsInsideClipPath(aClippedFrame, aPoint)) {
182 return false;
185 for (nsIFrame* kid = mFrames.FirstChild(); kid;
186 kid = kid->GetNextSibling()) {
187 nsISVGChildFrame* SVGFrame = do_QueryFrame(kid);
188 if (SVGFrame) {
189 gfxPoint pointForChild = point;
190 gfxMatrix m = static_cast<nsSVGElement*>(kid->GetContent())->
191 PrependLocalTransformsTo(gfxMatrix(), nsSVGElement::eUserSpaceToParent);
192 if (!m.IsIdentity()) {
193 if (!m.Invert()) {
194 return false;
196 pointForChild = m.Transform(point);
198 if (SVGFrame->GetFrameForPoint(pointForChild)) {
199 return true;
203 return false;
206 bool
207 nsSVGClipPathFrame::IsTrivial(nsISVGChildFrame **aSingleChild)
209 // If the clip path is clipped then it's non-trivial
210 if (nsSVGEffects::GetEffectProperties(this).GetClipPathFrame(nullptr))
211 return false;
213 if (aSingleChild) {
214 *aSingleChild = nullptr;
217 nsISVGChildFrame *foundChild = nullptr;
219 for (nsIFrame* kid = mFrames.FirstChild(); kid;
220 kid = kid->GetNextSibling()) {
221 nsISVGChildFrame *svgChild = do_QueryFrame(kid);
222 if (svgChild) {
223 // We consider a non-trivial clipPath to be one containing
224 // either more than one svg child and/or a svg container
225 if (foundChild || svgChild->IsDisplayContainer())
226 return false;
228 // or where the child is itself clipped
229 if (nsSVGEffects::GetEffectProperties(kid).GetClipPathFrame(nullptr))
230 return false;
232 foundChild = svgChild;
235 if (aSingleChild) {
236 *aSingleChild = foundChild;
238 return true;
241 bool
242 nsSVGClipPathFrame::IsValid()
244 if (mInUse) {
245 NS_WARNING("Clip loop detected!");
246 return false;
248 AutoClipPathReferencer clipRef(this);
250 bool isOK = true;
251 nsSVGEffects::GetEffectProperties(this).GetClipPathFrame(&isOK);
252 if (!isOK) {
253 return false;
256 for (nsIFrame* kid = mFrames.FirstChild(); kid;
257 kid = kid->GetNextSibling()) {
259 nsIAtom *type = kid->GetType();
261 if (type == nsGkAtoms::svgUseFrame) {
262 for (nsIFrame* grandKid = kid->GetFirstPrincipalChild(); grandKid;
263 grandKid = grandKid->GetNextSibling()) {
265 nsIAtom *type = grandKid->GetType();
267 if (type != nsGkAtoms::svgPathGeometryFrame &&
268 type != nsGkAtoms::svgTextFrame) {
269 return false;
272 continue;
274 if (type != nsGkAtoms::svgPathGeometryFrame &&
275 type != nsGkAtoms::svgTextFrame) {
276 return false;
279 return true;
282 nsresult
283 nsSVGClipPathFrame::AttributeChanged(int32_t aNameSpaceID,
284 nsIAtom* aAttribute,
285 int32_t aModType)
287 if (aNameSpaceID == kNameSpaceID_None) {
288 if (aAttribute == nsGkAtoms::transform) {
289 nsSVGEffects::InvalidateDirectRenderingObservers(this);
290 nsSVGUtils::NotifyChildrenOfSVGChange(this,
291 nsISVGChildFrame::TRANSFORM_CHANGED);
293 if (aAttribute == nsGkAtoms::clipPathUnits) {
294 nsSVGEffects::InvalidateRenderingObservers(this);
298 return nsSVGClipPathFrameBase::AttributeChanged(aNameSpaceID,
299 aAttribute, aModType);
302 void
303 nsSVGClipPathFrame::Init(nsIContent* aContent,
304 nsContainerFrame* aParent,
305 nsIFrame* aPrevInFlow)
307 NS_ASSERTION(aContent->IsSVG(nsGkAtoms::clipPath),
308 "Content is not an SVG clipPath!");
310 AddStateBits(NS_STATE_SVG_CLIPPATH_CHILD);
311 nsSVGClipPathFrameBase::Init(aContent, aParent, aPrevInFlow);
314 nsIAtom *
315 nsSVGClipPathFrame::GetType() const
317 return nsGkAtoms::svgClipPathFrame;
320 gfxMatrix
321 nsSVGClipPathFrame::GetCanvasTM(uint32_t aFor, nsIFrame* aTransformRoot)
323 return mMatrixForChildren;
326 gfxMatrix
327 nsSVGClipPathFrame::GetClipPathTransform(nsIFrame* aClippedFrame)
329 SVGClipPathElement *content = static_cast<SVGClipPathElement*>(mContent);
331 gfxMatrix tm = content->PrependLocalTransformsTo(gfxMatrix());
333 nsSVGEnum* clipPathUnits =
334 &content->mEnumAttributes[SVGClipPathElement::CLIPPATHUNITS];
336 return nsSVGUtils::AdjustMatrixForUnits(tm, clipPathUnits, aClippedFrame);
339 SVGBBox
340 nsSVGClipPathFrame::GetBBoxForClipPathFrame(const SVGBBox &aBBox,
341 const gfxMatrix &aMatrix)
343 nsIContent* node = GetContent()->GetFirstChild();
344 SVGBBox unionBBox, tmpBBox;
345 for (; node; node = node->GetNextSibling()) {
346 nsIFrame *frame =
347 static_cast<nsSVGElement*>(node)->GetPrimaryFrame();
348 if (frame) {
349 nsISVGChildFrame *svg = do_QueryFrame(frame);
350 if (svg) {
351 tmpBBox = svg->GetBBoxContribution(mozilla::gfx::ToMatrix(aMatrix),
352 nsSVGUtils::eBBoxIncludeFill);
353 nsSVGEffects::EffectProperties effectProperties =
354 nsSVGEffects::GetEffectProperties(frame);
355 bool isOK = true;
356 nsSVGClipPathFrame *clipPathFrame =
357 effectProperties.GetClipPathFrame(&isOK);
358 if (clipPathFrame && isOK) {
359 tmpBBox = clipPathFrame->GetBBoxForClipPathFrame(tmpBBox, aMatrix);
361 tmpBBox.Intersect(aBBox);
362 unionBBox.UnionEdges(tmpBBox);
366 nsSVGEffects::EffectProperties props =
367 nsSVGEffects::GetEffectProperties(this);
368 if (props.mClipPath) {
369 bool isOK = true;
370 nsSVGClipPathFrame *clipPathFrame = props.GetClipPathFrame(&isOK);
371 if (clipPathFrame && isOK) {
372 tmpBBox = clipPathFrame->GetBBoxForClipPathFrame(aBBox, aMatrix);
373 unionBBox.Intersect(tmpBBox);
374 } else if (!isOK) {
375 unionBBox = SVGBBox();
378 return unionBBox;